Shooter

Page 1

AndrĂŠ Santos 2090285@my.ipleiria.pt Jorge Mendes 2090300@my.ipleiria.pt

12/06/2012


Índice Índice ............................................................................................................................................. 2 Bem-Vindo..................................................................................................................................... 4 Instale as Ferramentas .............................................................................................................. 4 Escolha a sua plataforma .......................................................................................................... 4 Windows................................................................................................................................ 4 Xbox 360 ................................................................................................................................ 4 Windows Phone 7 ................................................................................................................. 4 Projectar o Jogo ............................................................................................................................. 6 Perguntas a Fazer ...................................................................................................................... 6 Fluxogramas de Design de Jogos ............................................................................................... 6 Criar um Jogador ........................................................................................................................... 8 Na Classe Player.cs .................................................................................................................... 9 Na Classe Game1.cs ................................................................................................................ 12 Executar o Jogo ....................................................................................................................... 13 Tratar os Comandos do Jogador ................................................................................................. 15 Na Classe Game1.cs ................................................................................................................ 15 Executar o Jogo ....................................................................................................................... 17 Animar o Jogador ........................................................................................................................ 18 Na Classe Animation.cs ........................................................................................................... 18 Na Classe Player.cs .................................................................................................................. 22 Na Classe Game1.cs ................................................................................................................ 24 Desenhar o fundo ........................................................................................................................ 25 Na Classe ParallaxingBackground.cs ....................................................................................... 25 Na Classe Game1.cs ................................................................................................................ 29 Executar o Jogo ....................................................................................................................... 30 Adicionar Inimigos ....................................................................................................................... 31 Na Classe Enemy.cs ................................................................................................................. 31 Na Classe Game1.cs ................................................................................................................ 34 Executar o Jogo ....................................................................................................................... 37 Cálculo das Colisões .................................................................................................................... 39 Na Classe Game1.cs ................................................................................................................ 39 Executar o Jogo ....................................................................................................................... 40 Disparar Coisas ............................................................................................................................ 42 Na Classe Projectile.cs ............................................................................................................. 42 Na Classe Game1.cs ................................................................................................................ 44


Executar o Jogo ....................................................................................................................... 47 Criar Explosões ............................................................................................................................ 48 Na Classe Game1.cs ................................................................................................................ 48 Executar o Jogo ....................................................................................................................... 50 Reproduzir Sons .......................................................................................................................... 51 Na Classe Game1.cs ................................................................................................................ 51 Executar o Jogo ....................................................................................................................... 52 Adicionar a Pontuação e a Vida do Jogador ................................................................................ 54 Na Classe Game1.cs ................................................................................................................ 54 Executar o Jogo ....................................................................................................................... 55 Acabando e Ir Mais Longe ........................................................................................................... 57 Adicionar Recursos .................................................................................................................. 57 Vender o Seu Jogo ................................................................................................................... 57 Fazer Jogos para a Xbox LIVE .................................................................................................. 57 Outras Questões...................................................................................................................... 57 Obrigado! ................................................................................................................................ 57


Bem-Vindo Bem-vindo ao tutorial de desenvolvimento de jogos 2D com o XNA Game Studio. Este tutorial irá guiá-lo através da criação de um jogo de acção em duas dimensões no Windows chamado Shooter. O Microsoft XNA é uma framework que serve para o desenvolvimento de jogos para PCs com Windows, para o consolas Xbox 360 e para dispositivos com Windows Phone 7. Ele é um substituto do Managed DirectX e pode ser descarregado gratuitamente. A ferramenta foi anunciada no dia 24 de Março de 2004 na Game Developers Conference em San José na Califórnia. Actualmente a sua versão mais completa é a 4.0 que foi lancada a 16 de Setembro de 2010. Neste tutorial, vai ser usada a linguagem de programação C#. O conhecimento prévio desta linguagem de programação é útil, mas não é obrigatório. Se quiser uma rápida introdução sobre os recursos da linguagem que vai usar, pode consultar o Visual C# Center no MSDN.

Instale as Ferramentas Para começar a desenvolver jogos é necessário realizar os seguintes passos: 1. Instalar o Visual C# 2010 Express; 2. Instalar o XNA Game Studio 4.0.

Escolha a sua plataforma O XNA Game Studio dá-lhe o poder para fazer jogos para Windows, Xbox 360 ou para Windows Phone 7. Dependendo da plataforma que escolher, pode haver alguns requisitos adicionais. Windows Não existem requisitos adicionais, está pronto para começar o seu projecto! Xbox 360 Se quiser desenvolver para a Xbox 360:  Vai precisar de uma consola Xbox 360 com um disco rígido e uma assinatura Silver ou Gold para Xbox LIVE;  Vai precisar de uma conta para se inscrever no App Hub;  Depois de ter uma conta, vá a App Hub e veja como ligar a sua XBOX com o XNA Game Studio.  Quando estiver ligado, estará pronto para começar o projecto! Windows Phone 7 Pode ainda optar por desenvolver para o seu Windows Phone 7 um jogo com o XNA Game Studio usando o emulador incluído nas ferramentas do programador do Windows Phone, ou você pode desenvolver num dispositivo Windows Phone 7 físico. Não há requisitos adicionais, se quer desenvolver no emulador, você já está pronto a começar a desenvolver o seu projecto. Se quer desenvolver num dispositivo físico Windows Phone 7:  Vai precisar do dispositivo móvel;  Vai precisar também de se inscrever para uma adesão no App Hub.


 

Uma vez que está inscrito, tem de desbloquear o seu telefone usando o guia disponível no site do App Hub; Você está pronto para começar o seu projecto!

Neste tutorial a plataforma que vai ser utilizada vai ser o Windows por uma questão de simplicidade uma vez que é a plataforma mais fácil de aprender numa fase inicial.


Projectar o Jogo O desenvolvimento de jogos é um desafio, o desafio de pensar em todos os aspectos para a programação dos vários componentes. Mesmo nas fases de elaboração, os jogos são complexos. O jogo que vai ser feito neste tutorial - Shooter - é um tipo particular de jogo com um conjunto de limites bem definidos em torno do que acontece quando o utilizador interage com ele. Este mapa conceitual é chamado de design do jogo.

Perguntas a Fazer Quando é criado um jogo, há um conjunto de perguntas que se devem fazer para começar a construir o mapa de design do jogo mentalmente.  Que tipo de jogo é?  Qual é o objectivo do jogo?  Quais são os elementos de jogabilidade?  Quais são os elementos de engenharia?  Que elementos de arte que você precisa? Esta é uma boa lista de considerações à partida. Vai descobrir mais, à medida que vai construindo o Shooter, mas em geral, se não tiver pelo menos o início de um projecto quando começa a desenvolver o seu jogo, provavelmente vai encontrar-se completamente perdido no jogo durante o seu desenvolvimento, e que pode até ser muito caro do ponto de vista de tempo, ainda mais se estiver a pagar a alguém para ajudá-lo a fazer o seu jogo ou se comprou arte ou ferramentas de que não vai sequer precisar.

Fluxogramas de Design de Jogos Criar um documento da concepção do jogo ajuda a aliviar possíveis armadilhas ao desenvolve-lo, o que uniformiza as perguntas acima e faz ressaltar as respostas para melhor ilustrar como funciona o seu jogo. Há muitas maneiras diferentes para criar um jogo, e a quantidade de conteúdo no projecto varia muito de jogo para jogo. Geralmente, quanto mais perto de estar desenvolvido o seu jogo estiver, melhor preparado estará para compreender e lidar com mudanças durante o seu desenvolvimento. Uma maneira potencial para criar um jogo é um fluxograma, destacando todos os caminhos diferentes através de componentes do jogo que um jogador tomaria.  O que pode o jogador vê, e o que pode optar por fazer?  Quando escolher algo, o que acontece? Na página seguinte está o fluxograma para o Shooter, que começa no topo com "Game Launch", e segue todo o caminho através dos vários componentes que o jogador pode escolher.


Um fluxograma do projecto é bom para jogos limitados. Você pode ver como até mesmo num pequeno jogo como o Shooter o fluxograma está a ficar grande. No entanto, os detalhes podem ser úteis para descrever o seu jogo para os outros - para os artistas, programadores e testers de modo que eles possam saber o que esperar e poderem trabalhar com essas expectativas.

O fluxograma acima é um mapa aproximado de como se vai criar o Shooter. Não vamos criá-lo exactamente na ordem de como ele é descrito acima, mas vai ver as várias peças a ser preenchidas à medida que o projecto vai sendo desenvolvido. Tudo o que resta agora é entrar e começar a construção. Vamos lá!


Criar um Jogador Vamos criar o nosso primeiro objecto e desenhá-lo no ecrã. Temos um projecto em branco, com os elementos multimédia prontos. Qual é a primeira coisa a fazer? Desenhar o personagem no ecrã. Podemos construir tudo a partir daí. Normalmente, os jogos representam um determinado tipo de objecto - como o personagem do jogador – através de uma classe, um conjunto de código que está relacionado e pode saltar de função para função numa única unidade. Vamos lá fazer um objecto.

Adicione uma nova classe no seu projecto, clicar com o botão direito em cima do projecto, escolher “Add” e carregar em “Class…” e dar o nome Player.cs à classe e carregar em “Add”.


A nova classe vai-se abrir. Este ficheiro irá conter todo o código que se escrever para definir a classe Player, todos os dados e funções que descrevem o que é um "player" no nosso jogo. Vamos começar com valores básicos como posição, e adicionar algumas funções que podemos chamar continuamente para pôr o jogador no ecrã e movê-lo mais tarde.

Na Classe Player.cs Olhe para as primeiras linhas do seu novo ficheiro de código. Existe algum código que já está escrito. É necessário apenas fazer algumas mudanças. Coloque o código abaixo, incluindo os using’s, de modo que seu código agora se pareça com este: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Shooter {

class Player { public void Initialize() { } public void Update() { } public void Draw() { } } }


Esses três itens dentro das {} - Initialize(), Update() e Draw() - são os métodos, também conhecidos como funções. Um método é um conjunto de instruções de código que, quando chamado por outro método, executa as instruções de cima para baixo. Veja que os métodos têm as suas próprias {} - o código que o método vai executar vai para dentro delas. Não há nada neles ainda. Vamos corrigir isso em breve. Primeiro, precisamos de adicionar alguns dados - mais especificamente, algumas variáveis que podem armazenar dados. Podemos pensar numa variável como um pedaço de dados que podem ser modificados por métodos. Às vezes os dados são um número, outras vezes é uma longa sequência de texto, e outras vezes são mesmo dados mais complexos, como gráficos ou texturas. Precisamos de adicionar uma variedade de dados que vai ser importante, tais como a posição no ecrã, a sua textura gráfica e assim por diante. Depois da primeira { na classe Player, acrescente as seguintes linhas: // Animation representing the player public Texture2D PlayerTexture; // Position of the Player relative to the upper left side of the screen public Vector2 Position; // State of the player public bool Active;

// Amount of hit points that player has public int Health; // Get the width of the player ship public int Width { get { return PlayerTexture.Width; } } // Get the height of the player ship

public int Height { get { return PlayerTexture.Height; } }

Notou que foram adicionados um par de métodos? Valores que têm os métodos get e set como acima são chamados propriedades, e são uma forma de controlar a forma como as variáveis são alteradas ou acedidas. Estamos agora a usar vários tipos de variáveis - vamos reflectir um pouco sobre esses tipos: int - um número inteiro, é um número inteiro como 0, 1 ou 42. bool - um booleano, esta é uma variável que pode ser verdadeira ou falsa.


Vector2 - um vector bidimensional, que tem um X e um Y que podem ser definidos independentemente um do outro. Texture2D - um gráfico bidimensional, este é um tipo de dados especial que contém dados gráficos que podem ser desenhados no ecrã. Estes tipos de dados irão trabalhar em conjunto para desenhar o nosso jogador. Quando desenhamos o personagem do jogador, vamos desenhar uma Texture2D num Vector2 dado. Vamos usar mais combinações de dados para fazer coisas como determinar a vida do jogador, determinar quando os objectos colidem uns com os outros, e reproduzir sons. Por enquanto, só precisamos ter o jogador no ecrã! O que precisamos de fazer é criar um método que irá definir a posição do nosso jogador e o gráfico inicial para as configurações corretas. Vamos definir mais alguns valores neste método que usaremos mais tarde. O método Initialize dentro da nossa classe Player funcionará bem. Só precisamos de fazer algumas alterações. Substitua o método Initialize() dentro da classe Player com o seguinte código: public void Initialize(Texture2D texture, Vector2 position) { PlayerTexture = texture; // Set the starting position of the player around the middle of the screen and to the back Position = position; // Set the player to be active

Active = true; // Set the player health Health = 100; }

Vai notar que há algumas coisas diferentes no que estamos a fazer: Os itens dentro dos () são os parâmetros do método. Quando chama um método, os parâmetros são os objectos com que deseja que o método trabalhe ou altere. O sinal = é um operador de atribuição - está a dizer ao método para definir o valor da variável do lado esquerdo do sinal de igual ao valor da variável do lado direito do sinal. Position = position é um operador de atribuição que está a definir o valor da posição na classe para o valor da posição que está na lista de parâmetros. Repare que é case-sensitive. Por que se tem que passar "position" como um parâmetro, mas não "Position"? Isto é porque Position é um valor que pertence à classe. Uma classe pode ler ou escrever para os seus próprios valores sem utilizar parâmetros. Criámos os valores que precisamos. Agora vamos combiná-los - a posição e o gráfico (Texture2D) - para desenhar o gráfico do jogador no ecrã! O último passo para desenhar o sprite é pôr a textura na fila para processamento. Isso vai ser feito passando a nossa textura para o SpriteBatch dentro do método Draw. Substitua o método Draw() dentro da classe Player com o seguinte código: public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(PlayerTexture, Position, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f); }


Foram feitas algumas coisas novas. A primeira coisa a notar é que está a ser chamado um método, num parâmetro. SpriteBatch é uma classe, assim como Player é uma classe. Tem métodos e variáveis. SpriteBatch.Draw() é um método. SpriteBatch.Draw() recebe um monte de parâmetros. Quando se chama um método, devem-se passar todos os parâmetros que ele pede. SpriteBatch.Draw() pede um Texture2D, um Vector2, (o que temos armazenado na classe Player como PlayerTexture e Position), e uma série de outros argumentos. Não se preocupe com os outros argumentos por agora - se a qualquer momento quiser saber o que são, clique sobre a função no código no Visual Studio, e clique no botão sublinhado a vermelho na imagem ao lado para ver uma dica que explica os parâmetros do método que está à procura. Terminámos o Player.cs por agora. Definimos o que é um "Player" através da criação de uma classe que possui todos os dados e métodos que são responsáveis por isso. O que precisamos de fazer agora é criar um desses objectos Player – o que é chamado de instanciação, e vamos fazer isso dentro do ficheiro Game1.cs. Abra o Game1.cs através de um duplo clique em "Game1.cs" no Solution Explorer.

Na Classe Game1.cs Game1.cs é um ficheiro de código muito importante - ele já está parcialmente preenchido. Este código é o loop do jogo. Vão ser feitas algumas alterações neste ficheiro para fazer a classe Player criar o loop do jogo. Quando fizermos isso, ele irá começar a desenhar no ecrã, cada frame. Comece no início do código, identificando a sequência public class Game1: Microsoft.Xna.Framework.Game. Algumas linhas abaixo, por baixo de SpriteBatch SpriteBatch, adicione estas linhas: // Represents the player Player player;

O que foi feito aqui é uma instância da classe Player. Uma instância de uma classe é chamada objecto. Agora, você pode passar esse objecto Player para diferentes métodos. Antes de usá-lo em qualquer lugar, no entanto, é preciso inicializá-lo. Veja o código abaixo, e procure o método chamado protected override void Initialize(). Dentro desse método, adicione estas linhas: // Initialize the player class player = new Player();

Está a ser usado um operador de atribuição para definir o nosso objecto igual a alguma coisa. Neste caso, é igual a uma nova (new) cópia da classe Player. A palavra-chave new é necessária sempre que se quiser criar um novo objecto. Isso informa ao dispositivo para alocar memória suficiente para caber o objecto. Se não se chamar o new nos objectos antes de os usar, ocorrerá um erro no programa. De seguida, tem de se carregar o gráfico do jogador e definir a sua posição inicial antes que possamos desenhá-lo no ecrã. Aqui é onde vai ser chamado o método Initialize da nossa classe Player. Uma vez que este envolve o carregamento de um gráfico do disco, vamos fazer isso dentro do método de loop do jogo LoadContent().


Veja para o código abaixo, e encontre o método chamado protected override void LoadContent(). Dentro desse método, abaixo do operador de atribuição SpriteBatch, adicione estas linhas: // Load the player resources Vector2 playerPosition = new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,GraphicsDevice.Viewport.TitleSafeArea.Y +GraphicsDevice.Viewport.TitleSafeArea.Height / 2); player.Initialize(Content.Load<Texture2D>("player"), playerPosition);

Estamos prontos para desenhar o nosso jogador. Sempre que se desenhar alguma coisa, vai ser chamado o método Draw() do loop do jogo. Olhe para o código abaixo, e procure o método chamado protected override void Draw(GameTime gameTime). Dentro desse método, depois de GraphicsDevice.Clear (Color.CornflowerBlue); adicione estas linhas: // Start drawing spriteBatch.Begin(); // Draw the Player player.Draw(spriteBatch); // Stop drawing spriteBatch.End();

E é esse que é o código para desenhar. Observe os métodos Begin() e End(). Qualquer desenho adicional que for feito tem de ser entre estes dois métodos.

Executar o Jogo O código escrito é o suficiente por agora - vamos então vê-lo em acção! Para executar o jogo, clique na seta verde apresentada na imagem ao lado. Se você carregar lá ainda não faz nada, não se esqueça de clicar com o botão direito do rato sobre o projecto Shooter na Solution Explorer e seleccione “Set as StartUp Project”, de seguida, clique na seta verde novamente. Se fez tudo certo, vai ver o jogo aparecer na janela do PC. Se houver algum erro, vai vêlos aparecer numa lista na parte inferior da janela do Visual Studio. O jogo será algo parecido com isto.



Tratar os Comandos do Jogador Agora que temos o desenho gráfico do jogador - o dirigível de ouro que viu na última etapa - podemos acrescentar um componente crítico que cada jogo tem: a capacidade de mover o jogador com base nos comandos de entrada. Nesta etapa, vamos adicionar a lógica dos comandos de entrada para que o dirigível possa ser movido pelo ecrã. Vamos começar no nosso ficheiro do jogo principal: Game1.cs.

Na Classe Game1.cs Deve ter notado que no método Update() do ficheiro Game1.cs, que até agora, não fizemos nada com ele. No entanto, é um componente crítico para o loop do jogo. Tal como o método Draw(), o método Update() é chamado automaticamente várias vezes num segundo (a taxa é de trinta vezes ou mais por segundo). Ao acompanhar o tempo gasto entre as chamadas para actualizar - o delta - podemos fazer mudanças incrementais em coisas como a posição do jogador (lembra-se da variável Player.Position?). Devido ao método Update() ser chamado muitas vezes por segundo, essas alterações irão aparecer como um movimento suave para o utilizador. O jogador pode utilizar vários dispositivos de entrada, dependendo da plataforma que escolher.  No Windows, o jogador tem o teclado, rato e, opcionalmente, um comando XBOX 360.  Na Xbox 360, o jogador tem um comando Xbox 360, ou qualquer outro controlo - como guitarras.  No Windows Phone 7, o jogador tem o toque e o acelerómetro. Os controlos do Shooter são todos acerca do movimento no ecrã - vamos optar por fazer apenas os comandos para a entrada do teclado. No entanto poderíamos escrever os comandos para a entrada para todas as três plataformas, todas no mesmo código, que não seria um problema. As entradas não utilizadas seriam simplesmente ignoradas dependendo da plataforma. Estamos prontos para entrar e começar a escrever o código dos comandos de entrada. Primeiro, precisamos de algumas variáveis que podem ser usadas para detectar o que está a ser carregado no teclado. Olhe para a primeira { no início da classe Game1, e posicione-se logo abaixo da instanciação feita na ultima etapa Player player;. Crie uma nova linha e, de seguida, adicione as linhas seguintes: // Keyboard states used to determine key presses KeyboardState currentKeyboardState; KeyboardState previousKeyboardState; // A movement speed for the player float playerMoveSpeed;

Repare, que nós criámos um valor float para representar a velocidade do movimento do jogador. O float é um número com uma parte fraccionária; é útil quando precisamos obter um valor mais específico do que apenas números inteiros. A velocidade do movimento do jogador não está directamente relacionada com os comandos de entrada - é a resposta a eles mas precisamos da variável mais à frente neste passo, por isso vamos criá-la agora.


Veja como a velocidade do movimento de cada jogador foi instanciado, mas não foi definido igual a nada. Precisamos então de fazer isso. Vamos fazer isso no método Initialize(), o mesmo que fizemos para algumas das variáveis que escrevemos na última etapa. Vá para o método Initialize(), e por baixo do código que você escreveu na última etapa, // Set a constant player move speed playerMoveSpeed = 8.0f;

player = new Player();, adicione esta linha: Bem, observe como a velocidade de movimento jogador foi definido como 8.0f. O f significa "valor float". É usado sempre que se deseja atribuir directamente um número com uma componente fraccionária a uma variável. O f distingue o valor a partir de um double, que é um tipo diferente de número fraccionário que geralmente não se utiliza no XNA Game Studio. Agora, estamos prontos para escrever o método de actualização real dos comandos de entrada. Poderíamos adicionar o nosso código directamente ao método Update(), mas há uma maneira melhor - devemos agrupá-lo num método nosso chamado UpdatePlayer(), e fazer todo o nosso trabalho lá, e só depois chamá-lo no Update(). Isso evitará que o método Update() fique confuso ao adicionar mais objectos ao jogo mais tarde. Vamos criar um novo método dentro de Game1.cs, chamado UpdatePlayer(), e adicionar os controlos para mover para cima, baixo, esquerda e direita. Para fazer isso, vai procurar um sítio vazio dentro da classe Game1, mas não dentro de outro método. Experimente este truque - olhe para o método Update(), encontre a primeira { depois do nome da função. Então, siga-a para baixo até encontrar uma } correspondente que tem o mesmo nível de indentação. Então, crie uma linha abaixo, e aí pode criar a nova função de forma segura. Adicione as linhas seguintes para criar o seu novo método UpdatePlayer: private void UpdatePlayer(GameTime gameTime) { // Use the Keyboard if (currentKeyboardState.IsKeyDown(Keys.Left)) { player.Position.X -= playerMoveSpeed; } if (currentKeyboardState.IsKeyDown(Keys.Right))

{ player.Position.X += playerMoveSpeed; } if (currentKeyboardState.IsKeyDown(Keys.Up)) { player.Position.Y -= playerMoveSpeed; }


if (currentKeyboardState.IsKeyDown(Keys.Down)) { player.Position.Y += playerMoveSpeed; } // Make sure that the player does not go out of bounds player.Position.X = MathHelper.Clamp(player.Position.X, 0,GraphicsDevice.Viewport.Width player.Width); player.Position.Y = MathHelper.Clamp(player.Position.Y, 0,GraphicsDevice.Viewport.Height player.Height); }

Este é um método grande, mas é bastante simples. Ele verifica todas as entradas de comandos possíveis, e responde de acordo com elas. No teclado, verifica se se está a pressionar para cima, baixo, esquerda ou para a direita, e se sim, altera o valor da posição do jogador uma quantidade adequada com base na acção que está a ser feita. O jogador move-se sempre a uma velocidade fixa desta maneira. Agora que temos o nosso método UpdatePlayer(), vamos chamá-lo a partir do método Update() do jogo para que cada vez que o método Update() for chamado pelo loop do jogo, ele chame o nosso método UpdatePlayer(). Qualquer coisa que queiramos actualizar durante o jogo deve ser feita através deste procedimento. Procure o método Update() dentro da classe Game1, e substitua a linha marcada “// TODO: Add your update logic here" com o seguinte código: // Save the previous state of the keyboard so we can determine single key presses previousKeyboardState = currentKeyboardState; // Read the current state of the keyboard and store it currentKeyboardState = Keyboard.GetState(); //Update the player UpdatePlayer(gameTime);

Executar o Jogo Escreveu todo o código para os comandos de entrada de que precisa para pôr o seu dirigível em movimento. Vamos testá-lo! Para executar o jogo, clique na seta verde apresentada na imagem ao lado. Se fez tudo certo, vai ver o jogo a aparecer no Windows. Se houver algum erro, vai vêlos aparecer numa lista na parte inferior da janela do Visual Studio. Tente dar ao jogo alguns comandos de entrada. Tente as setas do seu teclado.


Animar o Jogador Temos agora um dirigível que podemos mover pelo ecrã, mas sem animação não parece muito bom. Podemos consertar isso através da construção de um sistema de animação simples. A animação em jogos 2D é facilmente conseguida através da transição de um conjunto de imagens que são ligeiramente diferentes umas das outras muito rapidamente; é semelhante à forma como as alterações de posição, no último passo, deram a ilusão de movimento. Neste projecto, temos uma sprite sheet que inclui oito frames de animação para o nosso dirigível. Iremos percorrer essas frames continuamente ao longo do jogo para fazer a animação do dirigível. A primeira coisa que precisamos de fazer é criar uma classe chamada Animation.cs. Adicione uma nova classe no seu projecto, clicar com o botão direito em cima do projecto, escolher “Add” e carregar em “Class…” e dar o nome Animation.cs à classe e carregar em “Add”.

Na Classe Animation.cs Dentro do novo ficheiro de código Animation.cs a primeira coisa a fazer é substituir algumas linhas no topo. Vamos excluir todas as instruções using na parte superior, e substituílas com as seguintes linhas: // Animation.cs //Using declarations using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics;

E, tal como fizemos na classe Player, vamos adicionar todos as variáveis e os métodos que precisaremos para fazer com que esta classe funcione. Posicione o cursor logo após a { dentro da classe Animation. Crie uma nova linha e adicione as seguintes linhas: // The image representing the collection of images used for animation Texture2D spriteStrip; // The scale used to display the sprite strip float scale; // The time since we last updated the frame int elapsedTime;


// The time we display a frame until the next one

int frameTime; // The number of frames that the animation contains int frameCount; // The index of the current frame we are displaying int currentFrame; // The color of the frame we will be displaying Color color; // The area of the image strip we want to display Rectangle sourceRect = new Rectangle(); // The area where we want to display the image strip in the game Rectangle destinationRect = new Rectangle(); // Width of a given frame public int FrameWidth; // Height of a given frame public int FrameHeight; // The state of the Animation public bool Active; // Determines if the animation will keep playing or deactivate after one run public bool Looping; // Width of a given frame public Vector2 Position; public void Initialize() { } public void Update() { }


public void Draw() { }

Vai notar, que este código é muito parecido com o da classe Player, temos um Initialize(), um Update(), e um Draw(); as animações precisam de todos os três métodos para funcionarem correctamente. Vamos começar por preencher o método initialize(). Vamos adicionar alguns parâmetros que podemos passar sempre que criamos uma nova animação, e então vamos armazená-los para controlar a animação. Vamos então substituir o método Initialize() com esta versão: public void Initialize(Texture2D texture, Vector2 position, int frameWidth, int frameHeight, int

frameCount, int frametime, Color color, float scale, bool looping) { // Keep a local copy of the values passed in this.color = color; this.FrameWidth = frameWidth; this.FrameHeight = frameHeight; this.frameCount = frameCount; this.frameTime = frametime; this.scale = scale; Looping = looping; Position = position; spriteStrip = texture; // Set the time to zero elapsedTime = 0; currentFrame = 0; // Set the Animation to active by default Active = true; }

Como nas etapas anteriores, quando inicializa uma classe de um objecto, geralmente atribuem-se as variáveis do objecto a cópias dos valores que estão a entrar a partir dos parâmetros. Isto vai tornar muito acessível o uso desta classe - pode chamar o método Initialize() uma vez para cada objecto que desejar que use uma animação com diferentes gráficos, tempos e tamanhos, e cada objecto de animação pode ser executado independentemente utilizando as variáveis que foram passadas. Poderá ter uma animação para cada objecto do seu jogo! Agora, vamos preencher o método Update(). Como o método Update() da classe Game1, este método também funciona em GameTime - vamos usar este método para fazer o trabalho pesado para decidir se já passou ou não tempo suficiente na animação para activar a próxima frame, e fazer o cálculo para transformar a contagem de frames em pixéis reais que queremos que sejam desenhados no ecrã.


Substitua o mĂŠtodo Update() com esta versĂŁo: public void Update(GameTime gameTime) { // Do not update the game if we are not active if (Active == false) return; // Update the elapsed time elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds; // If the elapsed time is larger than the frame time // we need to switch frames if (elapsedTime > frameTime) { // Move to the next frame currentFrame++; // If the currentFrame is equal to frameCount reset currentFrame to zero if (currentFrame == frameCount) { currentFrame = 0;

// If we are not looping deactivate the animation if (Looping == false) Active = false; } // Reset the elapsed time to zero elapsedTime = 0; } // Grab the correct frame in the image strip by multiplying the currentFrame index by the

frame width sourceRect = new Rectangle(currentFrame * FrameWidth, 0, FrameWidth, FrameHeight); // Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width destinationRect = new Rectangle((int)Position.X - (int)(FrameWidth * scale) / 2, (int)Position.Y - (int)(FrameHeight * scale) / 2, (int)(FrameWidth * scale), (int)(FrameHeight * scale)); }


Todas as informações sobre quais pixéis da sprite sheet precisam de ser desenhados são armazenadas nos objectos sourceRect e destinationRect. Isto é o que o método Draw() irá receber para desenhar a frame certa no ecrã. Vamos completar o método Draw() e o nosso trabalho nesta classe estará terminado. Substitua o método Draw() com esta versão: // Draw the Animation Strip public void Draw(SpriteBatch spriteBatch) { // Only draw the animation when we are active if (Active) {

spriteBatch.Draw(spriteStrip, destinationRect, sourceRect, color); } }

Por agora é tudo para a classe Animation. Agora, qualquer objecto que queiramos desenhar mo ecrã pode ser animado com esta classe. Por enquanto, há apenas uma classe que queremos que o faça - é a classe Player. Agora, ela está a usar uma Texture2D para se representar graficamente mo ecrã. Vamos usar esta classe e dar-lhe um pouco de movimento. Vá para a classe Player clicando duas vezes no ficheiro Player.cs no Solution Explorer do Visual Studio.

Na Classe Player.cs Agora que estamos dentro da classe Player, vamos começar por substituir o Texture2D com a nossa nova classe de animação. No topo do ficheiro Player.cs, após a primeira { depois do inicio da classe Player, substitua a linha public Texture2D PlayerTexture; com o seguinte: // Animation representing the player public Animation PlayerAnimation;

Agora que removemos o Texture2D, há uma série de métodos que não funcionam, se tentou construir e executar agora. Precisamos de ir corrigi-los, começando pelo método Initialize(). Vá para baixo no ficheiro Player.cs e encontre o método Initialize(). Substitua-o com o seguinte código: // Initialize the player public void Initialize(Animation animation, Vector2 position) { PlayerAnimation = animation; // Set the starting position of the player around the middle of the screen and to the back Position = position;


// Set the player to be active Active = true; // Set the player health Health = 100; }

Temos também duas propriedades que têm dependência nas antigas variáveis de textura. Precisamos de alterá-las. Encontre as propriedades Width e Height na classe Player, e substitua-as com o seguinte: // Get the width of the player ship public int Width { get { return PlayerAnimation.FrameWidth; } } // Get the height of the player ship public int Height { get { return PlayerAnimation.FrameHeight; } }

Agora só faltam os métodos Draw() e Update(). Vamos corrigir o Draw(). Encontre o método Draw() e substitua-o com o seguinte: // Draw the player public void Draw(SpriteBatch spriteBatch) { PlayerAnimation.Draw(spriteBatch); }

Finalmente, encontre o método Update() na classe Player e substitua-o com o seguinte: // Update the player animation public void Update(GameTime gameTime) { PlayerAnimation.Position = Position; PlayerAnimation.Update(gameTime); }

E é tudo para a classe Player. Todo o desenho estático Texture2D foi substituído, e a classe agora dependente da classe Animation para se desenhar no ecrã. Agora temos que alterar duas coisas na classe Game1 para fazer com que esta mudança funcione. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.


Na Classe Game1.cs O passo que resta é dizer ao jogo para criar a animação e inicializá-la com um sprite sheet gráfico. O lugar apropriado para fazer isso é no método LoadContent(), onde estávamos anteriormente carregar a textura estática. Encontre o método LoadContent(). Encontre a linha player.Initialize(Content.Load <Texture2D>("player"), playerPosition); e substitua esse bloco com o seguinte: // Load the player resources Animation playerAnimation = new Animation(); Texture2D playerTexture = Content.Load<Texture2D>("shipAnimation"); playerAnimation.Initialize(playerTexture, Vector2.Zero, 115, 69, 8, 30, Color.White, 1f, true);

Vector2 playerPosition = new Vector2 (GraphicsDevice.Viewport.TitleSafeArea.X, GraphicsDevice.Viewport.TitleSafeArea.Y + GraphicsDevice.Viewport.TitleSafeArea.Height / 2); player.Initialize(playerAnimation, playerPosition);

E, finalmente, precisamos passar o tempo do loop do jogo ao método Update() da classe Player, portanto, para que o seu método Update() o possa passar para a classe Animation. Passar valores entre a cadeia de objectos que temos em breve vai passar a segundo plano. Para fazer isso, vamos colocar uma linha no método UpdatePlayer() que criou à um tempo atrás. Encontre o método UpdatePlayer(). Na parte superior do método, adicione a linha seguinte: player.Update(gameTime);

Agora vamos compilar e dar uma vista de olhos ao nosso trabalho. Deve ainda ser capaz de mover o dirigível, assim como na etapa anterior, mas agora, deve ver alguns efeitos esvoaçantes das velas e de vapor na parte de trás do dirigível. É a animação! Se você tiver erros, tente segui-los um pouco para ver se consegue corrigi-los. Pode facilmente clicar duas vezes num erro na lista de erros para ir automaticamente para o local do erro. Quando estiver pronto para seguir em frente, vamos explorar o parallaxing background.


Desenhar o fundo Temos agora uma animação, o dirigível, que está simplesmente a flutuar sobre um fundo azul. O próximo passo é utilizar um truque visual para dar a impressão de que não estamos simplesmente a flutuar no espaço azul, mas sim a mover-nos através das nuvens. Para isso poderíamos simplesmente desenhar uma única nuvem no fundo e movê-la da direita para a esquerda, mas podemos fazer melhor. Vamos desenhar um parallax background (fundo de parallax). O Parallax é um termo usado para desenhar várias camadas de imagens que se movem em velocidades diferentes, dando a ilusão de profundidade. Até agora, está acostumado a mudar a posição de um objecto vamos fazer a mesma coisa aqui. Já sabe como isto funciona agora - vamos criar uma classe para este novo tipo de objecto parallaxing background, escrever todas as funcionalidades necessárias dentro dela, em seguida, ligá-lo até dentro da nossa classe Game1. Vamos então começar a construir esta classe! Adicione uma nova classe no seu projecto, clicar com o botão direito em cima do projecto, escolher “Add” e carregar em “Class…” e dar o nome ParallaxingBackground.cs à classe e carregar em “Add”.

Na Classe ParallaxingBackground.cs Estamos dentro do nosso ficheiro de código novo. A primeira coisa a fazer é substituir algumas linhas no topo. Vamos eliminar todas as instruções using na parte superior, e substituí-las com as seguintes linhas: // ParallaxingBackground.cs using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics;

Agora, tal como fizemos na classe Animation, vamos criar todas as variáveis que vamos precisar e as funções Initialize(), Update() e Draw() para preencher mais tarde. Posicione o cursor logo após a { dentro da classe ParallaxingBackground. Crie uma nova linha e adicione as seguintes linhas: // The image representing the parallaxing background Texture2D texture; // An array of positions of the parallaxing background Vector2 [] positions; // The speed which the background is moving int speed;


public void Initialize() { } public void Update() { } public void Draw() { }

Como pode ver, não é um monte de dados. Tem uma Texture2D, e uma série de objectos Vector2, como mostrado pelos []. Um vector é uma forma de armazenar várias cópias do mesmo tipo de objecto e manipulá-los rapidamente. Verá mais vectores mais à frente. Finalmente foi usado um int para determinar a velocidade do fundo. Como mostramos no diagrama acima, teremos duas camadas parallaxing em cima de um fundo estático - assim, no final, vamos fazer dois desses objectos ParallaxingBackground dentro da classe Game1 e depois um Texture2D básico para a parte estática. A próxima coisa a fazer é começar a preencher os métodos dessa classe. Vamos começar com o Initialize(). Vamos escrever algum código que utiliza um caminho para um ficheiro gráfico e alguns parâmetros de tamanho e velocidade para preencher as variáveis da classe. Substitua o método Initialize() com esta versão: public void Initialize(ContentManager content, String texturePath, int screenWidth, int speed) { // Load the background texture we will be using texture = content.Load<Texture2D>(texturePath); // Set the speed of the background this.speed = speed; // If we divide the screen with the texture width then we can determine the number of tiles need. // We add 1 to it so that we won't have a gap in the tiling positions = new Vector2[screenWidth / texture.Width + 1]; // Set the initial positions of the parallaxing background for (int i = 0; i < positions.Length; i++) { // We need the tiles to be side by side to create a tiling effect positions[i] = new Vector2(i * texture.Width, 0); } }

Estamos a fazer algumas coisas complicadas aqui.


Primeiro, estamos a chamar o método Load() para inicializar o gráfico solicitado a partir do disco - que normalmente é feito dentro de Game1.LoadContent(); estamos simplesmente a passar o ContentManager que faz o carregamento dos gráficos e a fazê-lo aqui. De seguida, estamos a preencher o vector que mencionamos anteriormente. A nova linha Vector2[ScreenWidth/texture.Width+1]; está a dizer ao programa o tamanho que o vector deve ter - quantos objectos Vector2 vai suportar. Neste caso, está a dividir o tamanho do ecrã inteiro pelo tamanho da textura que estamos a usar para o fundo, em seguida, adicionando 1, por isso vamos sempre ter peças suficientes de fundo mais uma que está mais fora do ecrã para que vá aparecendo suavemente para dentro Finalmente, temos instanciado o vector, mas também precisamos de definir os valores dos objectos Vector2 dentro do vector. O loop for é como se se "andasse" no meio dos objectos dentro de um vector a interagir com eles. Começamos no primeiro objecto - que tem como índice de vector 0, escrito como [0] - e então avançamos um objecto de cada vez, e vamos fazer a operação dentro dos {} em cada objecto, até chegarmos ao final do vector designado por positions.Length. A variável i é um int que usamos como um contador. Na primeira execução do ciclo, i é 0, portanto, positions[i] é positions[0] - ou o primeiro elemento do vector. Na segunda vez do loop, i é incrementado em um (o operador i++ faz isso), e positions[i] fica positions[1] - ou o segundo elemento do vector. Nós estabelecemos que o elemento vai ser um Vector2 novo com um valor de X de i * texture.Width, e um valor de Y de 0. Usando um ciclo for assim, podemos executar a mesma operação para todos os elementos de um vector. Há muita coisa a acontecer aqui, mas vamos usar loops mais à frente - eles vão se tornar fáceis. Vamos passar para o método Update(). Dentro do método Update(), vamos mudar o nosso fundo, alterando o seu vector posição - semelhante à forma como nós movemos o dirigível do jogador dentro da classe Player, só vamos usar um valor fixo que não é controlado pelo jogador. Substitua o método Update() com esta versão: public void Update() { // Update the positions of the background for (int i = 0; i < positions.Length; i++) { // Update the position of the screen by adding the speed positions[i].X += speed; // If the speed has the background moving to the left if (speed <= 0) { // Check the texture is out of view then put that texture at the end of the screen


if (positions[i].X <= -texture.Width) { positions[i].X = texture.Width * (positions.Length - 1); } } // If the speed has the background moving to the right else { // Check if the texture is out of view then position it to the start of the screen

if (positions[i].X >= texture.Width * (positions.Length - 1)) { positions[i].X = -texture.Width; } } } }

Estamos a mover cada bloco do fundo ao longo de seu eixo do x - que é o movimento horizontal. Se a velocidade for positiva, a posição no eixo do x aumenta, o bloco move-se para a direita. Se a velocidade for negativa, a posição do eixo do x é ajustada, movendo-se o bloco para a esquerda. Se um bloco se tenha deslocado completamente após o final do ecrã, será reposto no início. Se pensar sobre o que estamos a fazer graficamente, é apenas um truque visual feito fora do ecrã. Como sempre temos mais um bloco de fundo que está fora do ecrã, vamos simplesmente continuar a trocá-lo, quando o bloco vai da direita para a esquerda completamente e desaparece para fora do ecrã, há que redefini-lo de volta para fora do ecrã à direita, por isso vai deslizar de volta para ver tudo de novo. Num bloco à vista, o jogador nunca percebe a mudança. Este ciclo vai durar para sempre e dar uma sensação de animação suave, sem lacunas. Estamos a fazer a actualização - agora precisamos de desenhar o fundo no método Draw(). Substitua o método Draw() com esta versão: public void Draw(SpriteBatch spriteBatch) { for (int i = 0; i < positions.Length; i++) { spriteBatch.Draw(texture, positions[i], Color.White); } }

Como vê, assim como no Initialize() e no Update(), estamos a fazer um loop através do vector de posições para fazer o nosso trabalho - aqui, estamos a chamar a textura gráfica no


ecrã em todas as posições armazenadas no vector de posições. Já definimos como é que o jogo com um fundo parallaxing se deve administrar. Agora precisamos de criar um par desses objectos na nossa classe Game1, e integrá-los nos métodos Initialize(), LoadContent(), Update() e Draw()do próprio Game1 para obtê-los no jogo. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Estamos dentro da nossa classe Game1 novamente. Assim como o jogador, precisamos de fazer alguns objectos, inicializá-los, carregá-los e colocá-los nos loops Update e Draw. Vamos começar! Olhe para a primeira { no início da classe Game1, e logo abaixo de float playerMoveSpeed adicionado no passo dos comandos de entrada. Crie uma nova linha e, em seguida, adicione o seguinte: // Image used to display the static background Texture2D mainBackground; // Parallaxing Layers ParallaxingBackground bgLayer1; ParallaxingBackground bgLayer2;

Aqui estão as nossas duas camadas de parallaxing e a camada de fundo estático. Agora, vamos inicializá-los. Olhe para o código, e encontre o método Initialize(). Dentro desse método, adicione estas linhas: bgLayer1 = new ParallaxingBackground(); bgLayer2 = new ParallaxingBackground();

Agora, vamos carregar as texturas adequadas a partir do disco no método LoadContent(). Olhe para o código, e encontre o método chamado protected override void LoadContent(). Dentro desse método, depois de player.Initialize, adicione estas linhas: // Load the parallaxing background bgLayer1.Initialize(Content, "bgLayer1", GraphicsDevice.Viewport.Width, -1); bgLayer2.Initialize(Content, "bgLayer2", GraphicsDevice.Viewport.Width, -2); mainBackground = Content.Load<Texture2D>("mainbackground");

Faltam apenas mais duas alterações - precisamos de fazer o Update e o Draw. Procure o método Update() na classe Game1, e após UpdatePlayer, adicione as seguintes linhas:


// Update the parallaxing background bgLayer1.Update(); bgLayer2.Update();

E finalmente vamos desenhar o fundo estático, e em seguida, os dois fundos parallaxing em cima dele. Cuidado aqui! A ordem em que executar esses métodos é muito importante. Quer desenhar o fundo primeiro, e só depois chamar o jogador. Se fizer isto ao contrário, o fundo vai ser desenhado em cima do jogador e vai escondê-lo. Procure o método Draw(). Dentro do método, antes de chamar player.Draw(), adicione estas linhas: spriteBatch.Draw(mainBackground, Vector2.Zero, Color.White); // Draw the moving background bgLayer1.Draw(spriteBatch); bgLayer2.Draw(spriteBatch);

E é isto! Está pronto para executar e verificar o seu trabalho.

Executar o Jogo Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado e dê uma olhada ao nosso trabalho. O jogo deve ser parecido com a imagem abaixo. Se isso não acontecer, ou estiver a ter um problema de compilação, reveja os erros do compilador e verifique novamente os passos.

Vamos avançar para o próximo passo - e um dos grandes. Vamos adicionar alguns bandidos.


Adicionar Inimigos Estamos a voar através das nuvens, com facilidade - mas o que é um jogo sem um desafio? É hora de adicionar outros personagens que também voam, já que temos o gráfico de um inimigo pronto a utilizar. Só falta criar uma classe para representar esses inimigos voadores, e de seguida, adicioná-los ao loop do jogo tal como foi feito para o jogador e para os fundos. Os inimigos serão representados por uma animação e terão uma posição, tal como o jogador, só que eles vão se mover da direita para a esquerda. E vamos precisar de mais do que um no jogo ao mesmo tempo, por isso vamos ter de gerir uma lista. Por fim, vamos baralhar um pouco e fazer com que apareçam aleatoriamente em posições verticais. Está pronto? Vamos lá! Adicione uma nova classe no seu projecto, clicar com o botão direito em cima do projecto, escolher “Add” e carregar em “Class…” e dar o nome Enemy.cs à classe e carregar em “Add”.

Na Classe Enemy.cs Estamos dentro da nossa nova classe Enemy. Vamos começar a trabalhar nas coisas básicas. Exclua todas as instruções using na parte superior do ficheiro, e substitua-os com as seguintes linhas: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;

Agora, como fizemos nas outras classes, vamos configurar todas as variáveis que vão ser precisas e os métodos padrão Initialize(), Update() e Draw() que vão ser preenchidos mais tarde. Posicione o cursor logo após a { dentro da classe Enemy. Crie uma nova linha e adicione as seguintes linhas: // Animation representing the enemy public Animation EnemyAnimation;

// The position of the enemy ship relative to the top left corner of thescreen public Vector2 Position; // The state of the Enemy Ship public bool Active; // The hit points of the enemy, if this goes to zero the enemy dies public int Health; // The amount of damage the enemy inflicts on the player ship public int Damage;


// The amount of score the enemy will give to the player public int Value; // Get the width of the enemy ship public int Width { get { return EnemyAnimation.FrameWidth; } } // Get the height of the enemy ship public int Height

{ get { return EnemyAnimation.FrameHeight; } } // The speed at which the enemy moves float enemyMoveSpeed; public void Initialize() { }

public void Update() { } public void Draw() { }

Isto está a começar a ficar repetitivo. Há apenas algumas coisas diferentes aqui, como algumas variáveis interessantes para a vida, danos, e pontuação. Vamos chegar a estas coisas numa etapa posterior, mas precisamos de nos focar primeiro no básico: a animação e a posição. Vamos começar com o método Initialize(). Substitua o método Initialize() por esta versão: public void Initialize(Animation animation,Vector2 position) { // Load the enemy ship texture EnemyAnimation = animation; // Set the position of the enemy Position = position;


// We initialize the enemy to be active so it will be update in the game Active = true; // Set the health of the enemy Health = 10; // Set the amount of damage the enemy can do Damage = 10; // Set how fast the enemy moves enemyMoveSpeed = 6f; // Set the score value of the enemy Value = 100; }

Aqui estão as variáveis de pontuação, vida, e dano novamente. Elas vão ser utilizadas numa etapa futura, quando começarmos a implementar a colisão e os danos. Por enquanto, vamos continuar a nossa tarefa e passar para o método Update() que irá mover os inimigos e verificar se eles saíram para fora do ecrã. Substitua o método Update() com esta versão: public void Update(GameTime gameTime)

{ // The enemy always moves to the left so decrement it's xposition Position.X -= enemyMoveSpeed; // Update the position of the Animation EnemyAnimation.Position = Position; // Update Animation EnemyAnimation.Update(gameTime);

// If the enemy is past the screen or its health reaches 0 then deactivateit if (Position.X < -Width || Health <= 0) { // By setting the Active flag to false, the game will remove this objet fromthe // active game list Active = false; } }

As três principais acções são já habituais - é preciso mover o inimigo a uma velocidade fixa, de seguida, enviar a nova posição e actualizá-lo. Finalmente vamos verificar se o objecto ultrapassou o limite esquerdo do ecrã. A referência a –Width (-Largura) significa um valor igual a -1*Width, que é a largura do gráfico do inimigo. Isso acontecerá quando o pixel mais à direita


do gráfico do inimigo toca no lado esquerdo do ecrã, para que o resto do gráfico deslize suavemente para fora do ecrã. Configurando a variável Active=false; é o que vai eliminar o inimigo quando este sai para fora do ecrã. Vamos fazer a remoção com base nesta flag na classe Game1. Vamos lá desenhar! Substitua o método Draw() com esta versão: public void Draw(SpriteBatch spriteBatch) { // Draw the animation EnemyAnimation.Draw(spriteBatch); }

Uma simples chamada do Draw, da mesma forma como desenhámos o Player. É utilizado o mesmo código na animação em ambos, e é essa a vantagem de utilizar funções de encapsulamento como a animação - tudo pode usar as mesmas funções básicas. Terminamos a classe Enemy por agora - precisamos de criar o nosso loop do jogo, por isso vamos trabalhar na classe Game1.cs no resto desta etapa. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Estamos de volta à nossa classe Game1. Agora, se nós estivéssemos interessados apenas num único inimigo tal como temos um único jogador, as coisas seriam muito simples. Adicionávamos uma classe Enemy e preenchiamos os loops Draw() e Update(), e ficava feito! No entanto, no Shooter, existe uma grande quantidade de inimigos. Queremos ser capazes de controlar tantos quantos quisermos, todos a moverem-se ao mesmo tempo. Para fazer isso, precisamos de algo como um vector - vamos usar uma classe especial que funciona como um vector, que pode aumentar e diminuir automaticamente de tamanho para caber qualquer número de objectos que queiramos (até um certo limite). É chamada List e a sua configuração não é muito difícil. Ela também se comporta como um vector, e podemos percorrê-la usando loops assim como fazemos para os vectores. Olhe para a primeira { no início da classe Game1, e vá logo abaixo da linha ParallaxingBackground bgLayer; que adicionou na etapa anterior. Crie uma nova linha e, em seguida, adicione o seguinte: // Enemies Texture2D enemyTexture; List<Enemy> enemies; // The rate at which the enemies appear TimeSpan enemySpawnTime; TimeSpan previousSpawnTime;


// A random number generator Random random;

Não estamos apenas a adicionar uma lista de inimigos, estamos a adicionar algumas variáveis que vão ajudar a controlar a frequência a que queremos adicionar novos inimigos, bem como a classe Random, que gera números aleatórios que podemos utilizar em qualquer altura, e vamos usá-los neste jogo para fazer variar a posição vertical dos inimigos quando eles são criados para parecer que estão a vir de todo o lado. Vamos precisar de fazer algumas inicializações a essas variáveis novas. Olhe para o código, e encontre o método Initialize(). Dentro desse método, adicione estas linhas: // Initialize the enemies list enemies = new List<Enemy> (); // Set the time keepers to zero previousSpawnTime = TimeSpan.Zero; // Used to determine how fast enemy respawns enemySpawnTime = TimeSpan.FromSeconds(1.0f); // Initialize our random number generator random = new Random();

Agora, e uma vez que o inimigo tem gráficos associados a ele, vamos precisar de adicionar uma linha ao método LoadContent(), igual ao que fizemos para os fundos e para o jogador. Olhe para o código, e encontre o método LoadContent(). Dentro desse método, depois da linha mainBackground = Content.Load<Texture2D>("mainbackground");, adicione estas linhas: enemyTexture = Content.Load<Texture2D>("mineAnimation");

Sem problemas! Agora vai ficar um pouco mais complicado. Vamos criar o nosso próprio método chamado AddEnemy(). Este é o método que vamos chamar quando estivermos prontos para adicionar um novo inimigo ao jogo. Vamos ter que fazer algumas configurações de inicialização e posição. Para fazer isso, vai precisar de algum espaço vazio na classe Game1, mas não dentro de um método. Tal como quando adicionou o método UpdatePlayer(), procure o método Update(), encontre a primeira { depois do nome da função. Então, vá para baixo até encontrar uma } correspondente que está no mesmo nível de indentação. Então, vá uma para linha abaixo, crie uma nova linha, e estará pronto para continuar. Adicione as seguintes linhas para fazer o seu novo método AddEnemy():


private void AddEnemy() { // Create the animation object Animation enemyAnimation = new Animation(); // Initialize the animation with the correct animation information enemyAnimation.Initialize(enemyTexture, Vector2.Zero, 47, 61, 8, 30,Color.White, 1f, true); // Randomly generate the position of the enemy Vector2 position = new Vector2(GraphicsDevice.Viewport.Width +enemyTexture.Width / 2, random.Next(100, GraphicsDevice.Viewport.Height -100)); // Create an enemy Enemy enemy = new Enemy(); // Initialize the enemy enemy.Initialize(enemyAnimation, position); // Add the enemy to the active enemies list enemies.Add(enemy); }

Este método não é muito diferente, excepto a randomização do valor da position.Y - os 100 valores estão a definir a área real onde o inimigo é permitido voar, não pode ser muito alta nem muito baixa, mas mais perto do centro do ecrã. Agora que temos o método de para adicionar, precisamos de criar um método que actualize todos os inimigos que temos, bem como determine se devemos ou não adicionar um novo inimigo com base na quantidade de tempo que já passou. Assim como a função AddEnemy, crie um espaço vazio na classe Game1. Adicione as seguintes linhas para criar o método UpdateEnemies(): private void UpdateEnemies(GameTime gameTime) { // Spawn a new enemy enemy every 1.5 seconds

if (gameTime.TotalGameTime - previousSpawnTime > enemySpawnTime) { previousSpawnTime = gameTime.TotalGameTime; // Add an Enemy AddEnemy(); } // Update the Enemies for (int i = enemies.Count - 1; i >= 0; i--) {


enemies[i].Update(gameTime); if (enemies[i].Active == false) { enemies.RemoveAt(i); } } }

Este método faz duas coisas - primeiro, verifica o GameTime. Se se passou um segundo ou mais desde que um inimigo foi adicionado, ele adiciona outro inimigo e faz reset ao cronómetro. Em segundo lugar, ele usa um loop para percorrer todos os inimigos actuais e verifica se a flag está como Active. Lembra-se da validação que fizemos em Enemy.cs que iria definir Active para false se o inimigo tivesse saído do ecrã? Agora vamos verificar esse valor e fazer alguma coisa com ele. Vamos chamar a função RemoveAt que remove o objecto Enemy da lista, eliminando-o. Ao eliminar objectos "mortos", conseguimos manter a quantidade de memória que utilizamos a um nível razoável. Se não removermos os objectos, a lista iria estar sempre a crescer, e eventualmente estourar o limite de memória disponível no dispositivo e iria causar um erro. Preservar a memória é importante! Agora que fizemos o método UpdateEnemies(), precisamos de chamar o nosso loop principal do jogo em algum sitio, por isso precisamos chama-lo no método Update(). Procure o método Update() dentro da classe Game1, e depois de bgLayer2.Update(), adicione as seguintes linhas: // Update the enemies UpdateEnemies(gameTime);

Finalmente chegámos ao último método – tal como o jogador e o fundo, é preciso desenhar os inimigos no método Draw(). Agora, o sítio onde desenha os inimigos é onde quiser, desde que seja após o fundo. Se desenhar depois do jogador, os inimigos vão aparecer por cima do dirigível do jogador. Se os desenhar antes de o jogador, os inimigos irão aparecer por atrás do dirigível do jogador. Procure o método Draw(). Dentro do método, depois de bgLayer2.Draw(), adicione estas linhas: // Draw the Enemies for (int i = 0; i < enemies.Count; i++) { enemies[i].Draw(spriteBatch); }

Executar o Jogo Este foi um grande passo, vamos dar vista de olhos! Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado. Deve ver agora as minas inimigas que atravessam o ecrã como na imagem abaixo.



Cálculo das Colisões Agora, temos o nosso jogador e os nossos inimigos a voar através das nuvens. Eles estão em perfeita harmonia - nenhum dos dois se incomoda com o outro. Vamos mudar isso, através da implementação de código que detecta se o jogador e os inimigos se tocam um ao outro, e responde se o fizerem. Normalmente, isto é conhecido como detecção de colisão, e é como vamos causar danos ao jogador e aos inimigos. O trabalho que vamos fazer nesta etapa vai nos permitir implementar projécteis na próxima etapa, com que o jogador poderá atirar e destruir os inimigos. Por agora, vamos criar um algoritmo de detecção de colisão básico, a funcionar entre a classe do jogador e a lista de objectos inimigos. Ao contrário dos últimos passos, não vamos precisar de uma nova classe - vamos fazer todo o nosso trabalho na classe Game1. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Estamos prontos para implementar o nosso algoritmo de detecção de colisões. Uma vez que tem alguns passos específicos, devemos colocá-lo no seu próprio método, como fizemos para o jogador e para os objectos inimigos. Vamos chamá-lo de UpdateCollision(). Para fazer isso, encontre um espaço vazio dentro da classe Game1, não dentro de outro método. Assim como quando adicionou o método UpdateEnemies(), procure o método Update(), encontrar a primeira { depois do nome da função. Então, siga-a para baixo até encontrar uma } correspondente que tem o mesmo nível de indentação. Então, crie uma linha abaixo, e estará pronto para prosseguir. Adicione as seguintes estas linhas para fazer o seu novo método UpdateCollision(): private void UpdateCollision() { // Use the Rectangle's built-in intersect function to // determine if two objects are overlapping Rectangle rectangle1; Rectangle rectangle2; // Only create the rectangle once for the player rectangle1 = new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Width, player.Height); // Do the collision between the player and the enemies for (int i = 0; i <enemies.Count; i++) { rectangle2 = new Rectangle((int)enemies[i].Position.X, (int)enemies[i].Position.Y, enemies[i].Width, enemies[i].Height);


// Determine if the two objects collided with each other if(rectangle1.Intersects(rectangle2)) { // Subtract the health from the player based on the enemy damage player.Health -= enemies[i].Damage; // Since the enemy collided with the player destroy it enemies[i].Health = 0; // If the player health is less than zero we died if (player.Health <= 0)

player.Active = false; } } }

Vamos ver então que é que o nosso método está a fazer. Em última análise, o nosso jogador e os objectos inimigos são representados graficamente por uma textura rectangular no ecrã. Há partes curvas, claro, mas são apenas pixéis que não são desenhados. Todos os ficheiros gráficos que normalmente são usados em jogos são rectangulares (mesmo que o desenho real no ficheiro não o seja), e podemos usar isso para criar um tipo simples de colisão que verifica se os dois gráficos estão sobrepostos. Se estiverem, eles "colidem" e podemos responder de acordo com isso. Usamos a classe Rectangle para fazer exactamente isso - é uma classe que tem métodos auxiliares que ajudam a determinar se dois rectângulos se interceptam. Simplesmente temos de definir o tamanho e a posição dos rectângulos para terem o mesmo tamanho e posição dos objectos no nosso jogo, e fazer o teste de intersecção. Sem dúvida, percebe que existem algumas desvantagens neste sistema. Quanto mais curvo o objecto é desenhado, menos preciso esta colisão será, porque os pixéis que o utilizador vê não correspondem com os rectângulos que estão a colidir. A resolução deste problema muitas vezes é feita através da implementação de um sistema de colisão "pixelperfect". É um bom tema para um estudo posterior - por enquanto, vamo-nos manter em movimento! Falta apenas um passo neste sistema - simplesmente temos que chamar o nosso método UpdateCollision() dentro do ciclo Update() do jogo. Procure o método Update() dentro da classe Game1, e depois de UpdateEnemies() adicione as seguintes linhas: // Update the collision UpdateCollision();

Executar o Jogo Vamos verificar o nosso trabalho! Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado. Sabe que a colisão funciona se


observar que os inimigos desaparecem quando o dirigível vai contra eles. Este é o sinal de que estamos prontos para seguir em frente. Pode ver que o jogador realmente nunca morre, mesmo que o código acima faça uma alteração em Player.Active quando sua saúde vai abaixo de zero. Está a acontecer, mas a variável Ative não é verificada por nada. Vamos alterar isso em breve. Primeiro, vamos dar a este sistema de colisão um bom uso, vamos adicionar projécteis, que o jogador pode disparar para destruir os inimigos.


Disparar Coisas Enquanto já percorremos um longo caminho para dar movimento ao jogador e aos inimigos, a única maneira que temos actualmente de o jogador interagir com os inimigos é ir contra eles, o que causa danos ao jogador, assim como aos inimigos. Isso não é uma estratégia que dure muito tempo. Vamos dar ao jogador uma arma para poder usar. O tipo mais simples de arma é apenas um fluxo automático de disparo de projécteis que vão da esquerda para a direita, começando a partir da posição do jogador. Em muitos aspectos, eles são parecidos com a classe Enemy, mas a direcção é oposta e tem alguma lógica diferente. E como o inimigo, estes projécteis devem estar na sua própria classe. Vamos criar uma classe nova de projéctil. Adicione uma nova classe no seu projecto, clicar com o botão direito em cima do projecto, escolher “Add” e carregar em “Class…” e dar o nome Projectile.cs à classe e carregar em “Add”.

Na Classe Projectile.cs Dentro da nossa nova classe, vamos fazer uma modificação rápida na seção superior. Exclua todos os using da parte superior, e substitua-os com as seguintes linhas: // Projectile.cs //Using declarations

using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;

Agora, tal como fizemos nas outras classes, vamos configurar todas as variáveis que vão ser necessárias e os métodos Initialize(), Update() e Draw() que vamos preencher mais tarde. // Image representing the Projectile public Texture2D Texture; // Position of the Projectile relative to the upper left side of the screen public Vector2 Position; // State of the Projectile public bool Active; // The amount of damage the projectile can inflict to an enemy public int Damage; // Represents the viewable boundary of the game Viewport viewport;


// Get the width of the projectile ship public int Width { get { return Texture.Width; } } // Get the height of the projectile ship public int Height { get { return Texture.Height; } } // Determines how fast the projectile moves float projectileMoveSpeed; public void Initialize() { } public void Update() { }

public void Draw() { }

Como pode ver, as variáveis e os métodos são muito semelhantes aos da classe Enemy. Veja que estes projécteis não têm uma animação associada, são apenas uma textura estática. Além disso vão se mover tão rápido que uma animação dificilmente valeria a pena. No entanto, com algumas alterações facilmente poderiam ser animados. Estas variáveis precisam de ser preenchidas, então vamos fazer o nosso método Initialize() para tratarmos disso. Substitua o método Initialize() por esta versão: public void Initialize(Viewport viewport, Texture2D texture, Vector2 position) { Texture = texture; Position = position; this.viewport = viewport; Active = true; Damage = 2; projectileMoveSpeed = 20f; }


Criámos os valores iniciais - vamos agora para o método Update(), onde vamos decidir com que velocidade os projécteis se vão mover e quando é que devem ser considerados "mortos". Substitua o método Update() com esta versão: public void Update() { // Projectiles always move to the right Position.X += projectileMoveSpeed; // Deactivate the bullet if it goes out of screen if (Position.X + Texture.Width / 2 > viewport.Width)

Active = false; }

Finalmente, precisamos de um método Draw() para garantir que os projécteis são desenhados no ecrã exactamente como os outros objectos. Substitua o método Draw() com esta versão: public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(Texture, Position, null, Color.White, 0f, new Vector2(Width / 2, Height / 2), 1f, SpriteEffects.None, 0f); }

A classe Projectile.cs está completamente definida. A maior parte do que fizemos é tão simples quanto (ou até mais simples do que) o que fizemos na classe Enemy. E, como na classe Enemy, vamos gerar muitos muito rapidamente. Para esse trabalho, vamos precisar de mexer na classe Game1, criando uma lista e algumas variáveis de tempo. Vamos passar para a classe Game1. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Estamos de volta à nossa classe Game1, e vamos fazer alguns passos já normais, começando por adicionar a lista e as variáveis temporais ao topo da classe. Encontre a primeira { no início da classe Game1, e vá para depois de Random random; que adicionou na etapa anterior. Crie uma nova linha e, de seguida, adicione o seguinte: Texture2D projectileTexture; List<Projectile> projectiles; // The rate of fire of the player laser TimeSpan fireTime; TimeSpan previousFireTime;

Essas são as variáveis de que precisamos - vamos inicializa-las. Encontrar o método Initialize(). Dentro desse método, adicione estas linhas:


projectiles = new List<Projectile>(); // Set the laser to fire every quarter second fireTime = TimeSpan.FromSeconds(.15f);

Uma vez que os projécteis têm um gráfico associado, vamos precisar de carrega-lo a partir do disco no LoadContent(). Encontre o método LoadContent(). Nesse método, depois de enemyTexture = call, adicione estas linhas: projectileTexture = Content.Load<Texture2D>("laser");

Agora vamos adicionar os nossos próprios métodos personalizados, começando com um método AddProjectile(), semelhante ao método AddEnemy(). Este método, similar à sua contraparte Enemy, vai criar um novo projéctil e adicioná-lo à lista que criamos. Encontre algum espaço vazio dentro da classe Game1, de preferência perto do método Update(), e faça uma nova linha, então estará pronto para continuar. Adicione as seguintes linhas para criar o seu método AddProjectile(): private void AddProjectile(Vector2 position) { Projectile projectile = new Projectile(); projectile.Initialize(GraphicsDevice.Viewport, projectileTexture,position); projectiles.Add(projectile); }

Agora, precisamos de chamar o método AddProjectile(). Uma vez que o jogador é quem está a disparar os projécteis, faz sentido colocar a chamada dentro do método UpdatePlayer(). Vá para o método UpdatePlayer() na classe Game1, e adicione as seguintes linhas ao final: // Fire only every interval we set as the fireTime if (gameTime.TotalGameTime - previousFireTime > fireTime) { // Reset our current time previousFireTime = gameTime.TotalGameTime; // Add the projectile, but add it to the front and center of the player AddProjectile(player.Position + new Vector2(player.Width / 2, 0)); }

Estamos a adicionar projécteis, mas precisamos de algum código de actualização para lidar com os que já estão no ar. Nós escrevemos um código parecido com este antes, não será difícil criar um método UpdateProjectiles(). Encontre algum espaço livre na classe Game1 fora de qualquer método. Adicione as seguintes linhas para fazer o método UpdateProjectiles():


private void UpdateProjectiles() { // Update the Projectiles for (int i = projectiles.Count - 1; i >= 0; i--) { projectiles[i].Update(); if (projectiles[i].Active == false) { projectiles.RemoveAt(i); }

} }

Temos de chamar UpdateProjectiles(), é claro - um bom lugar para isso é dentro do método Update(). Procure o método Update() na classe Game1, e após a chamada do método UpdateCollision(), adicione as seguintes linhas: // Update the projectiles UpdateProjectiles();

Perfeito. Já estamos a adicionar projécteis, e estamos a actualizá-los, precisamos apenas de desenhá-los. Adicione o seguinte código ao método Draw(), depois de enemies.Draw(): // Draw the Projectiles for (int i = 0; i < projectiles.Count; i++) { projectiles[i].Draw(spriteBatch); }

O que falta? Eles vão nascer, mover-se, e desenhar-se - mas não vão colidir com nada ainda. Precisamos de adicionar o código ao método UpdateCollision() para que leve em conta estes projécteis. Queremos percorrer a lista para cada projéctil e para cada um, fazer um loop intercalado dentro desse loop, onde vamos percorrer a lista dos inimigos inteira e verificar as colisões. Isto parece ser um monte de cálculos. À medida que o jogo se torna maior, é necessário pensar em maneiras de reduzir o número de cálculos, mas por agora, verificar se todos estão a colidir contra todos vai servir. Procure o método UpdateCollision() na classe Game1, e na parte inferior do método, adicione as seguintes linhas:


// Projectile vs Enemy Collision

for (int i = 0; i < projectiles.Count; i++) { for (int j = 0; j < enemies.Count; j++) { // Create the rectangles we need to determine if we collided with each other rectangle1 = new Rectangle((int)projectiles[i].Position.X projectiles[i].Width / 2,(int)projectiles[i].Position.Y projectiles[i].Height / 2,projectiles[i].Width, projectiles[i].Height); rectangle2 = new Rectangle((int)enemies[j].Position.X - enemies[j].Width / 2, (int)enemies[j].Position.Y - enemies[j].Height / 2, enemies[j].Width, enemies[j].Height); // Determine if the two objects collided with each other if (rectangle1.Intersects(rectangle2)) { enemies[j].Health -= projectiles[i].Damage; projectiles[i].Active = false; } } }

E já está! Temos projécteis, e eles são mortais contra os inimigos. Vamos dar uma vista de olhos.

Executar o Jogo Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado. Deve ser óbvio se estiver tudo bem - deve ver projécteis em grande quantidade quando jogar o jogo, semelhante ao ecrã abaixo. Quando os projécteis acertam nos inimigos, os inimigos devem absorver alguns tiros e depois desaparecem.


Criar Explosões Um jogo é criado pela mistura do desafio com a recompensa. Tal como está agora, o nosso desafio actual para o jogador é formidável - os inimigos aparecem rapidamente e é difícil fazê-los desaparecer todos com rapidez suficiente. Mas o que acontece com uma recompensa? Apenas ver os inimigos desaparecerem quando são destruídos não é bom o suficiente. Vamos fazer algo cativante – que tal uma explosão? Para criar um efeito de explosão, vamos usar dois conceitos que já fizemos no nosso código, e colocá-los juntos. Vamos usar uma Sprite Sheet gráfica na classe Animation, mas que combine com a natureza do tempo e com a mecânica da Lista que usámos para as classes Enemy e Projectile. As explosões serão animações que ficam tempo suficiente para serem reproduzidas uma vez e depois serem removidas. Não precisamos de uma nova classe para isto, vamos apenas trabalhar na classe Game1 com alguma gestão básica de listas como já fizemos. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Isto vai ser fácil. Tudo o que vai ser feito já foi feito antes, de uma forma ou de outra. Você é um profissional! Primeiro, precisamos de declarar algumas variáveis no topo da classe Game1. Olhe para a primeira { no início da classe Game1, e vá para depois de TimeSpan previousFireTime;. Adicione uma nova linha e, em seguida, adicione o seguinte: Texture2D explosionTexture; List<Animation> explosions;

Precisamos de inicializar a lista e a textura. Vamos começar pela lista, no Initialize(). Olhe para o código, e encontre o método Initialize(). Dentro desse método, adicione esta linha: explosions = new List<Animation>();

Agora, a textura. Isso é feito no ContentLoad(). Encontre o método LoadContent(). Dentro desse método, depois de projectileTexture = call, adicione esta linha: explosionTexture = Content.Load<Texture2D>("explosion");

Assim como no projéctil, precisará de uma maneira fácil de adicionar uma explosão quando for necessário. Crie o seu próprio método AddExplosion(); encontre algum espaço vazio na classe Game1 e adicione estas linhas: private void AddExplosion(Vector2 position) {


Animation explosion = new Animation(); explosion.Initialize(explosionTexture,position, 134, 134, 12, 45, Color.White, 1f,false); explosions.Add(explosion); }

Quando é que vamos chamar AddExplosion()? Esse é o truque. Precisamos de encontrar um sítio onde tomamos uma decisão sobre um inimigo que tenha acabado de ser destruído. Encontrou o sítio? É no UpdateEnemies(). Temos que ser cuidadosos sobre o sítio onde vamos pôr as próximas linhas - deve ser no lugar certo. Encontre o método UpdateEnemies(). Procure a linha if (enemies[i].Active == False) e vá para depois da {. Adicione as seguintes linhas: // If not active and health <= 0 if (enemies[i].Health <= 0) { // Add an explosion AddExplosion(enemies[i].Position); }

Excelente. Assim como nos projécteis, vamos precisar de criar um método para actualizar as explosões e removê-las da lista. Chame a este método UpdateExplosions(). Encontrar um espaço vazio na classe Game1, de preferência perto do método Update(), e crie uma nova linha. Adicione as seguintes linhas para criar o seu novo método UpdateExplosions(): private void UpdateExplosions(GameTime gameTime) { for (int i = explosions.Count - 1; i >= 0; i--) { explosions[i].Update(gameTime); if (explosions[i].Active == false) { explosions.RemoveAt(i); }

} }

E, claro, devemos chamar este método novo no método Update() no loop do jogo. Procure o método Update() na classe Game1, e depois de UpdateProjectiles(), adicione as seguintes linhas: // Update the explosions UpdateExplosions(gameTime);

Finalmente precisamos de desenhar as explosões activas no ecrã. Assim como nos projécteis, é só adicionar as seguintes linhas para no Draw():


// Draw the explosions for (int i = 0; i < explosions.Count; i++) { explosions[i].Draw(spriteBatch); }

Estamos prontos para construir e ver as explosões!

Executar o Jogo Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado. Quando correr, deverá ver uma explosão sempre que derrubar um dirigível inimigo. Se o jogo não se parecer com a imagem abaixo, refaça os seus passos ao longo desta seção.


Reproduzir Sons Fizemos alguns progressos no nosso jogo para nos concentrármos na entrada de comandos e nos gráficos. Eles são os componentes-chave de um jogo, mas há um herói que também é importante: o som. Os efeitos sonoros e a música podem fazer um jogo excepcional, e sem eles, os jogadores podem achar que falta algo ao jogo. Temos alguns eventos no jogo em que seria bom ter alguns efeitos sonoros, e um bom som é sempre útil para fazer um jogo shoot-em-up bom. Vamos pôr sons e música no nosso jogo. Vamos trabalhar dentro Game1.cs. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Há duas classes de que precisamos para fazer objectos - SoundEffect e Song. Elas são bastante auto-explicativas, e o carregamento é muito parecido ao dos gráficos. A primeira coisa a fazer é declará-las no topo da nossa classe. Olhe para a primeira { no início da classe Game1, e vá para depois de List<Animation> explosions;. Crie uma nova linha e, em seguida, adicione o seguinte: // The sound that is played when a laser is fired SoundEffect laserSound; // The sound used when the player or an enemy dies SoundEffect explosionSound; // The music played during gameplay Song gameplayMusic;

Agora, já que está tudo nos conteúdos, vamos carregar esses objectos dentro do método LoadContent(). Uma nota: não só vamos carregar os itens, mas vamos iniciar a reprodução do item Song dentro do método LoadContent(). Encontre o método LoadContent(). Dentro desse método, depois de explosionTexture = call, adicione estas linhas: // Load the music

gameplayMusic = Content.Load<Song>("sound/gameMusic"); // Load the laser and explosion sound effect laserSound = Content.Load<SoundEffect>("sound/laserFire"); explosionSound = Content.Load<SoundEffect>("sound/explosion"); // Start the music right away PlayMusic(gameplayMusic);

Vai perceber que estamos a chamar PlayMusic(). Se estava a tentar construir, agora vai ficar um pouco surpreso - PlayMusic() não existe! Vamos fazer este método, mas é fácil.


Encontre um espaço vazio na classe Game1 que não esteja dentro de outro método. Crie uma nova linha e, em seguida, adicione o seguinte: private void PlayMusic(Song song) { // Due to the way the MediaPlayer plays music, // we have to catch the exception. Music will play when the game is not tethered try { // Play the music MediaPlayer.Play(song);

// Loop the currently playing song MediaPlayer.IsRepeating = true; } catch { } }

Agora que já temos musica, vamos criar os efeitos sonoros. Temos dois - o laser e a explosão. Existe um lugar perfeito para cada uma delas. Primeiro, o laser deve ser em UpdatePlayer(), logo depois de AddProjectile(), para que quando o jogador dispara um projéctil, o som seja reproduzido imediatamente. Vá para baixo e encontre o método UpdatePlayer(). Procure a linha AddProjectile(player.Position + new Vector2 (player.Width/2, 0); e abaixo, adicione as seguintes linhas: // Play the laser sound laserSound.Play();

Vamos também fazer com que as explosões pareçam boas. As explosões acontecem no interior de UpdateEnemies() quando a vida dos inimigos chega a zero. Vamos colocar o nosso efeito sonoro e chamá-lo lá. Encontre o método UpdateEnemies(). Procure a linha AddExplosion(enemies[i].Position); e abaixo, adicione as seguintes linhas: // Play the explosion sound

explosionSound.Play();

E já está! Os efeitos sonoros e a música estão prontos assim que crie e execute o jogo.

Executar o Jogo Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado para ouvir os efeitos sonoros e a música a funcionar. Deve estar a ouvir um laser sempre que o jogador dispara, uma explosão sempre que um inimigo é destruído, e deve haver música de fundo a tocar.


Se n茫o estiver a ouvir os efeitos sonoros, verifique o seu c贸digo com o c贸digo acima para ter certeza que tem tudo.


Adicionar a Pontuação e a Vida do Jogador Estamos quase no fim, este é o passo final no Shooter, e o último exercício deste tutorial. Quando pensa em jogos, a acção normalmente vem-lhe à cabeça, mas o que pode não ser tão óbvio são os elementos que ajudam a controlar o seu progresso e as coisas decisivas como a pontuação, a vida e o bónus. Juntos, estes elementos no ecrã são conhecidos como a interface do utilizador e são uma parte de um bom jogo. Vamos criar uma interface para o utilizador muito básica com uma variável de pontuação e uma variável de vida, colocando-as juntos ao topo esquerdo do ecrã usando um SpriteFont, uma classe que desenha o texto no ecrã como um elemento gráfico normal. Isto é tão simples que podemos fazer tudo na classe Game1.cs. Vá para a classe Game1 clicando duas vezes no ficheiro Game1.cs no Solution Explorer do Visual Studio.

Na Classe Game1.cs Dentro da classe Game1, vamos adicionar as variáveis necessárias no topo da classe e, de seguida, vamos inicializá-las, carregá-las, actualizá-las e desenhá-las - o mesmo que temos feito a quase todos os nossos outros objectos. Vá para a primeira { no início da classe Game1, e vá para depois de Song gameplayMusic;. Crie uma nova linha e, em seguida, adicione o seguinte: //Number that holds the player score int score; // The font used to display UI elements SpriteFont font;

Vamos desenhar tanto a pontuação como a vida, mas já temos um valor para a vida em Player - vamos usar esse em vez de um novo valor para a vida. Agora que temos o tipo de letra e a variável pontuação instanciados, vamos inicializá-los, começando com a variável pontuação dentro do Initialize(). No método Initialize(), depois de explosions = new List(), adicione esta linha: //Set player's score to zero score = 0;

Uma vez que o SpriteFont está nos conteúdos, é preciso carregá-lo através do método LoadContent(). No método LoadContent(), antes de PlayMusic(), adicione o seguinte: // Load the score font font = Content.Load<SpriteFont>("gameFont");

Estamos prontos para iniciar a actualização e o desenho da pontuação e da vida. A vida está a ser actualizada automaticamente na classe Player devido ao que fizemos nos passos anteriores. No entanto, vamos ter que adicionar uma chamada para actualizar a pontuação. Vamos querer fazer isso sempre que destruamos um inimigo, seja disparando ou embatendo


com o nosso dirigível. Vamos fazer isso em UpdateEnemies(). Cada vez que a vida do inimigo chega a 0, adicionamos um valor na pontuação do jogador igual ao valor da pontuação que foi definida no inimigo. No método UpdateEnemies() depois de explosionSound.Play();, adicione as seguintes linhas: //Add to the player's score score += enemies[i].Value;

Estamos a actualizar os valores. Finalmente é necessário apenas de chamar estes valores, com um texto antes deles no ecrã. Vamos usar o método Draw(). Encontre o método Draw(), e insira as seguintes linhas depois da } depois de explosions[i].Draw: // Draw the score spriteBatch.DrawString(font, "score: " + score, new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X, GraphicsDevice.Viewport.TitleSafeArea.Y), Color.White); // Draw the player health spriteBatch.DrawString(font, "health: " + player.Health, new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X, GraphicsDevice.Viewport.TitleSafeArea.Y + 30), Color.White);

E, finalmente, uma vez que ainda não foi feito nada com o valor da vida, vamos fazer um pouco de lógica onde reinicializamos a pontuação do jogador se a sua vida chagar a zero ou menos. Vamos fazer isso no fim do método UpdatePlayer(): // reset score if player health goes to zero if (player.Health <= 0) { player.Health = 100; score = 0; }

Já está! É só criar e executar o jogo para ver todo o código em acção e a trabalhar.

Executar o Jogo Crie e execute o jogo clicando na seta verde apresentada na imagem ao lado, e vamos ver como fica. Deverá ver a pontuação e a vida no canto superior esquerdo, a serem actualizados à medida que destruir os inimigos e sofrer danos. Deve ficar parecido com a imagem seguinte:



Acabando e Ir Mais Longe Parabéns por terminar este tutorial! Acabou de completar o Tutorial de desenvolvimento de um jogo 2D, e aprendeu os conceitos básicos do Shooter que funcionará na plataforma Windows. Aqui ficam algumas respostas a perguntas comuns.

Adicionar Recursos Existem inúmeros recursos que pode adicionar a este projecto para aumentar a jogabilidade, adicionar gráficos mais atraentes ou som, usar recursos avançados de entrada, ou até mesmo mudar o jogo para uma experiência totalmente nova. Existe um número de amostras, tutoriais e utilitários para fazer a adição fácil de funcionalidades. O código é totalmente gratuito e pode reutilizá-lo nos jogos - até mesmo jogos que possa vender. Procure o “Education Catalog” no App Hub para ficar com uma ideia dos tipos de código de exemplo que existem disponíveis.

Vender o Seu Jogo A Microsoft tem dois mercados onde pode vender os jogos que faz utilizando o XNA Game Studio, um para a Xbox 360 e um para o Windows Phone 7. Vá ao Hub App para ficar a saber como funcionam as vendas e o que tem de fazer para começar a vender os seus jogos.

Fazer Jogos para a Xbox LIVE A Microsoft gere um conjunto de jogos excepcionais que se expande a cada dia. Se realmente quer chegar às estrelas e fazer jogos para o Xbox LIVE Arcade ou para a Xbox LIVE do Windows Phone 7, vai querer ler sobre os programas disponíveis. Vá ao App Hub e veja como se pode tornar um profissional de elite no desenvolvimento de jogos.

Outras Questões Se tiver outras perguntas que não estão respondidas aqui, existem recursos para ajudar. Vá à página de desenvolvimento de jogos do App Hub onde poderá obter recursos de desenvolvimento de jogos, incluindo o catálogo de ensino, fóruns e recursos da comunidade.

Obrigado! Parabéns novamente por concluir este tutorial, e obrigado por usar o XNA Game Studio. Estamos ansiosos para ver os grandes jogos que irá criar!


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.