Desenvolvendo em XNA

GameDev #13: É hora de fazer chover asteroides no seu jogo

No último artigo da coluna GameDev você finalmente iniciou o desenvolvimento do seu primeiro jogo em XNA. Tudo bem que ele não é lá nenh... (por Sergio Oliveira em 22/04/2013, via Xbox Blast)

GameDev #13: É hora de fazer chover asteroides no seu jogo

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.

Esse é o seu jogo neste momento. A nave passeia tranquilamente no espaço sideral.

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:


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!

Agora seu jogo parece mais interessante. Asteroides surgem a todo momento para desafiá-lo.

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
Sergio Oliveira escreve para o Xbox Blast sob a licença Creative Commons BY-SA 3.0. Você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original do mesmo.

Comentários

Google+
Disqus
Facebook