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