No último artigo da coluna GameDev você finalmente iniciou o desenvolvimento do seu primeiro jogo em XNA. Tudo bem que ele não é lá nenhuma obra prima da atual geração de videogames, mas cumpre exatamente seu papel: aplicar e fixar todos os conceitos e conhecimentos adquiridos nos primeiros artigos da coluna. Além, claro, de desenvolver um game simples e jogável. No artigo de hoje você incluirá os asteroides que nomeiam o game e desenvolverá a sua lógica.
Nunca é demais lembrar que para executar o que é desenvolvido nesta coluna, é necessária a instalação do Microsoft Visual C# 2010 Express e do XNA Game Studio, ambos gratuitos.
A última versão do projeto “Asteroides” também está disponível. Acesse http://sdrv.ms/Zxykjf para realizar o download do código-fonte.
Criando os asteroides
O último artigo foi finalizado com o jogo funcionando parcialmente: tem-se um cenário intergaláctico e a destemida nave desbravadora do espaço sideral, podendo ser controlado por você. Mas como o nome do jogo sugere, “Asteroides” precisa ter alguns asteroides para adicionar emoção à coisa toda e dificultar sua viagem no espaço.
Os conceitos utilizados para criar a espaçonave no último artigo serão os mesmos usados para criar os asteroides. A única diferença é que a posição inicial dos asteroides e a movimentação deles dependerão de um fator aleatório.
Crie uma nova classe no projeto – da mesma forma como foi feita a classe da espaçonave –, dê o nome “Asteroide” a ela e adicione o seguinte código:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace Asteroides
{
    /// <summary>
    /// Esse é um componente de jogo que implementa os asteroides que o jogador deve desviar
    /// </summary>
    public class Asteroide : Microsoft.Xna.Framework.DrawableGameComponent
    {
        protected Texture2D textura;
        protected Rectangle spriteRetangulo;
        protected Vector2 posicao;
        protected int Yvelocidade;
        protected int Xvelocidade;
        protected Random random;
        //Largura e altura do sprite na textura
        protected const int LarguraAsteroide = 45;
        protected const int AlturaAsteroide = 45;
        public Asteroide(Game game, ref Texture2D aTextura)
            : base(game)
        {
            textura = aTextura;
            posicao = new Vector2();
            //Cria o retângulo
            //Isso irá representar aonde a imagem do sprite está na tela
            spriteRetangulo = new Rectangle(20, 16, LarguraAsteroide, AlturaAsteroide);
            //Inicializa o gerador de número aleatório e coloca o asteroide na posição inicial
            random = new Random(this.GetHashCode());
            ColocarNaPosicaoInicial();
        }
        /// <summary>
        /// Inicializa a posição e velocidade do asteroide
        /// </summary>
        protected void ColocarNaPosicaoInicial()
        {
            posicao.X = random.Next(Game.Window.ClientBounds.Width - LarguraAsteroide);
            posicao.Y = 0;
            Yvelocidade = 1 + random.Next(9);
            Xvelocidade = random.Next(3) - 1;
        }
        /// <summary>
        /// Permite que o componente do jogo se desenhe na tela
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            //Captura o spritebatch atual
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            //Desenha o asteroide
            sBatch.Draw(textura, posicao, spriteRetangulo, Color.White);
            base.Draw(gameTime);
        }
        /// <summary>
        /// Permite que o componente do jogo atualize a si próprio
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            //Verifica se o asteroide ainda está visível - ou no espaço da tela
            if ((posicao.Y >= Game.Window.ClientBounds.Height) ||
                (posicao.X >= Game.Window.ClientBounds.Width) ||
                (posicao.X <= 0))
            {
                ColocarNaPosicaoInicial();
            }
            //Move o asteroide
            posicao.Y += Yvelocidade;
            posicao.X += Xvelocidade;
            base.Update(gameTime);
        }
        /// <summary>
        /// Verifica se o asteroide intersecta com o retângulo especificado (rect)
        /// </summary>
        public bool VerificarColisao(Rectangle rect)
        {
            Rectangle retanguloSprite = new Rectangle((int)posicao.X, (int)posicao.Y,
                LarguraAsteroide, AlturaAsteroide);
            //Perceba o uso da função Intersects que já faz todo o trabalho por você
            return retanguloSprite.Intersects(rect);
        }
    }
}
O método ColocarNaPosicaoInicial põe o asteroide numa posição horizontal aleatória no topo da tela, além de obter a velocidade de deslocamento horizontal e vertical do asteroide, que muda a cada chamada ao método Update da classe.
O método VerificarColisao verifica se o retângulo que delimita o meteoro intersecta com o retângulo passado por parâmetro. É esse retângulo passado por parâmetro que delimita a posição da espaçonave do jogador na tela.
Agora, vamos colocar os asteroides na tela. Adicione o seguinte código no método Start na classe Game1:
//Adiciona os asteroides
for (int i = 0; i < ContadorInicialAsteroides; i++)
{
   Components.Add(new Asteroide(this, ref asteroidsTexture));
}
A constante ContadorInicialAsteroides define o número inicial de asteroides que aparecerão no jogo. Declare essa constante na classe Game1 como segue:
private const int ContadorInicialAsteroides = 10;
Salve o seu projeto e o execute pressionando F5. Vejam só, o jogo está ganhando vida e já temos uma belíssima chuva de asteroides. Perceba que cada asteroide se comporta de maneira independente, da mesma forma que a espaçonave.
Codificando a lógica do jogo
“Asteroides” está quase pronto. A nave já desbrava os N cantos do universo e inúmeros asteroides põem nossa habilidade de piloto à prova. Agora é necessário fazer com que esses componentes trabalhem juntos; ou seja, quando a espaçonave do jogador colidir com um asteroide, o jogo irá recomeçar.
Da mesma forma que há um método Start que inicializa todos os componentes do jogo, será um método chamado DoLogicaJogo que executará a lógica do jogo. Nesse estágio de desenvolvimento, esse método irá apenas percorrer os componentes do jogo e checará se um asteroide colidiu com a espaçonave do jogador. Se houver uma colisão, os asteroides deverão ser retirados do jogo para serem recriados na reinicialização dele.
/// <summary>
/// Executa a lógica do jogo
/// </summary>
private void DoLogicaJogo()
{
   //Verifica colisões
   bool temColisao = false;
   Rectangle retanguloNave = player.GetBounds();
   foreach (GameComponent gc in Components)
   {
      if (gc is Asteroide)
      {
         temColisao = ((Asteroide)gc).VerificarColisao(retanguloNave);
         if (temColisao)
         {
            //Remove todos os asteroides anteriores
            RemoveTodosAsteroides();
            //Reinicia o jogo
            Start();
            break;
         }
      }
   }
}
/// <summary>
/// Remove todos os asteroides do jogo
/// </summary>
private void RemoveTodosAsteroides()
{
   for (int i = 0; i < Components.Count; i++)
   {
      //Verifica se o componente da vez é um asteroide
      if (Components[i] is Asteroide)
      {
         //Remove o asteroide da vez
         Components.RemoveAt(i);
         i--;
      }
   }
}
Você deve chamar o método DoLogicaJogo dentro do método Update da classe Game1, imediatamente antes da linha que chama a ação base.Update(gameTime). A invocação do método nesse ponto fará com que a lógica seja executada a cada loop.
Agora tente executar o jogo para ver que quando sua espaçonave colide com um asteroide. O programa colocará todos os objetos em suas respectivas posições iniciais, e continuará o loop até que o usuário feche a janela.
Tudo muito bonito e funcionando, mas ainda é preciso adicionar mais desafio ao jogo. Incrementar a quantidade de asteroides na tela à medida que o tempo for passando parece bastante razoável, certo? Como os asteroides se comportam independentemente, você só precisará adicionar um novo componente de jogo que representa o asteroide.
Para fazer isso, escreva o método ChecarNovoAsteroide como segue abaixo e o chame dentro de DoLogicaJogo depois do loop foreach.
/// <summary>
/// Verifica se está na hora de soltar outro asteroide
/// </summary>
private void ChecarNovosAsteroides()
{
   //Adiciona um asteroide a cada TempoAdicionarAsteroide
   if ((System.Environment.TickCount - lastTickCount) > TempoAdicionarAsteroide)
   {
      lastTickCount = System.Environment.TickCount;
      Components.Add(new Asteroide(this, ref asteroidsTexture));
      asteroideCount++;
   }
}
A variável TempoAdicionarAsteroide será uma constante que representará o intervalo, em milissegundos, que um novo asteroide deverá ser adicionado. Declare-a na classe Game1 conforme segue:
private const int TempoAdicionarAsteroide = 5000; //Representa um novo asteroide a cada 5 segundos
Esse será o número mágico que define que a cada 5 segundos um novo asteroide deverá aparecer na tela. Perceba que você pode modificá-lo para que esse intervalo seja maior ou menor.
Dois novos atributos armazenarão a quantidade de asteroides adicionados (asteroideCount) e o tempo para calcular o intervalo desejado (lastTickCount). Declare-os da seguinte forma:
private const int TempoAdicionarAsteroide = 5000; //Representa um novo asteroide a cada 5 segundos
private int lastTickCount;
private int asteroideCount;
Agora você deve inicializar esses atributos no método Start. Adicione o seguinte código ao método:
//Inicializa o contador
lastTickCount = System.Environment.TickCount;
//Reseta o contador de asteroides
asteroideCount = ContadorInicialAsteroides;
E claro, para que o método ChecarNovosAsteroides faça seu papel, ele deverá ser chamado no método DoLogicaJogo, caso contrário todo trabalho terá sido em vão. Adicione-o após o loop foreach:
Assim, a cada 5 segundos, um novo asteroide será adicionado ao jogo.
foreach (GameComponent gc in Components)
{
   if (gc is Asteroide)
   {
       temColisao = ((Asteroide)gc).VerificarColisao(retanguloNave);
       if (temColisao)
       {
          //Se houver colisão, então o som de explosão é tocado
          explosao.Play();
          //Remove todos os asteroides anteriores
          RemoveTodosAsteroides();
          //Reinicia o jogo
          Start();
          break;
       }
   }
}
ChecarNovosAsteroides();Assim, a cada 5 segundos, um novo asteroide será adicionado ao jogo.
Execute o projeto e voilá! Seu “Asteroides” está funcionando e você está jogando ele. E os asteroides não param de aparecer!
Veja só quão o jogo já foi incrementado. Já temos algo minimamente divertido e desafiador. A pequena nave está encurralada numa chuva de asteroides sem fim! Finalmente você criou os asteroides e adicionou alguma (boa) lógica para o jogo. Tudo foi feito conforme o planejado na GameDev 12.
Apesar da sensação de dever cumprido, ainda podemos fazer mais por esse jogo. Como saber exatamente quantos asteroides estão na tela? E a pontuação do jogador? Onde ela está? E onde estão os sons do nosso jogo? Será esse um jogo mudo?
Calma, meu jovem desenvolvedor. Essas e outras perguntas serão respondidas no próximo artigo. Até lá, programe, pratique e caso tenha dúvidas, envie um e-mail para mim em sergioliveira@nintendoblast.com.br.
Nos vemos na próxima, até lá!
Você pode dar uma espiadinha no que foi feito no artigo de hoje fazendo o download do projeto em http://sdrv.ms/ZrGfm0
Revisão: Jaime Ninice
Capa: Douglas Fernandes



 




 
 

 
 
 
 
 

Comentários