Pular para o conteúdo
Início » Gamedev » Tutorial: XNA Invasores – Parte 6

Tutorial: XNA Invasores – Parte 6

Google News

Hoje vamos ver algumas coisas bem interessantes. Finalmente vamos instanciar um Game Component, e vamos também aprender a trabalhar com imagens e ler o estado das teclas do teclado. E muita atenção, hoje veremos ainda como deixar o seu jogo com uma velocidade constante, independente do computador onde ele está rodando.

Para começar, crie uma nova classe com o nome de NaveJogador e faça ela herdar da classe Nave, de acordo com o que foi modelado no Diagrama de Classes.

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Tutorial_XNAInvasores
{
    class NaveJogador : Nave
    {
    }
}

NaveJogador possui três atribuitos básicos (e outros dois atributos que serão vistos mais à frente):

  • podeAtirar: controla se o jogador pode ou não disparar seus tiros.
  • vidas: conta a quantidade de vidas restantes.
  • pontos: marca a pontuação do jogador.
private bool podeAtirar;
private int vidas;
private int pontos;

No construtor, vamos definir sua velocidade (atributo herdado de Nave) como valendo 100, definir que inicialmente ele não pode atirar, estipular a quantidade de vidas inicial como 2 (este é o total de vidas restantes, o jogo termina quando vidas < 0) e sua pontuação como 0.

public NaveJogador(JogoInvasores jogo, Texture2D imagem, Vector2 coord)
    : base(jogo, imagem, coord)
{
    velocidade = 100.0f;
    podeAtirar = false;
    vidas = 2;
    pontos = 0;
}

Duas propriedades precisam ser definidas pra que seja possível acessar a quantidade de vidas e a pontuação externamente:

public int Pontos
{
    get { return pontos; }
    set { pontos = value; }
}
public int Vidas
{
    get { return vidas; }
    set { vidas = value; }
}

O método Update irá fazer a movimentação da Nave, porém antes precisamos fazer uma alteração na classe JogoInvasores.
Lembram que eu comentei sobre a velocidade de execução do jogo e como temos desempenho diferente em computadores diferentes? Pois é, ao implementar o movimento das Naves, pela primeira vez lidamos com algo que depende do desempenho do jogo.
Se nós definirmos que queremos que a nave se mova 10 pixels para a esquerda sempre que o usuário pressionar a seta esquerda do teclado, a nave irá se mover estes 10 pixels à cada atualização do jogo. Supondo 60 quadros por segundo, ao final de um segundo ela andaria 600 pixels. Porém, num PC mais modesto, o jogo poderia rodar a 30 quadros por segundo e passado novamente um segundo teríamos um movimento de apenas 300 pixels.
Resolver isto é mais simples do que parece. Ao invés de mandar que a nave se mova em valores absolutos (10 pixels, 30 pixels…), vamos mandar que ela se mova a uma velocidade X (no caso de NaveJogador, esta velocidade foi definida como 100) vezes um fator de tempo que é o intervalo de tempo passado entre a última atualização e a iteração atual.
Fazendo este cálculo, quando mais rápido o jogo correr, menor será este intervalo de tempo e menor a distância percorrida a cada iteração. Porém, como uma velocidade maior, mais atualizações vão ocorrer por segundo e a distância total percorrida vai ser a distância desejada. Do outro lado, se o jogo estiver lento, o intervalo de tempo entre as atualizações será maior, resultando num deslocamento mais alto a cada iteração, porém com um total de iterações por segundo menor.
Em termos de código, precisamos declarar um atributo em JogoInvasores para armazenar este intervalo de tempo. Vamos chamá-lo de deltaTempo:

private float deltaTempo;

E depois criamos uma propriedade para acessar este atributo:

public float DeltaTempo
{
    get { return deltaTempo; }
}

Feito isso, vamos alterar o método Update de JogoInvasores para que ele calcule o deltaTempo. Neste método, você verá que o modelo criado pelo Visual C# já incluiu um comando que verifica se um botão do controle do XBOX 360 foi pressionado para finalizar o jogo. Como não vamos trabalhar com este controle, simplesmente remova este comando.
O cálculo do deltaTempo é simples. Usamos o objeto GameTime (que contém dados sobre o tempo passado desde o início do jogo) e acessamos sua propriedade EllapsedRealTime (que armazena o tempo passado desde a última atualização do jogo). Feito isso, pegamos o total de milissegundos passados usando a propriedade TotalMilliseconds e finalizamos o cálculo dividindo o valor obtido por 1000, para que o deltaTempo seja um valor decimal (se não fizer esta divisão, basta mudar a escala da velocidade definida para as naves).

deltaTempo =
    (float)gameTime.ElapsedRealTime.TotalMilliseconds / 1000.0f;

Repare que uma instância de GameTime é passada nos métodos Update e Draw de todas as nossas entidades, portanto seria possível fazer este cálculo diretamente no método Update. Porém isto não é feito, senão o deltaTempo iria variar para cada entidade e teríamos movimento com velocidades diferentes.
Voltando a mexer na NaveJogador, criamos o método Update e vamos trabalhar nele:

public override void Update(GameTime gameTime)
{
}

A primeira coisa a ser feita é obter o estado corrente do Teclado. Isto é feito através de Keyboard.GetState() que está no Namespace Microsoft.Xna.Framework.Input (este Namespace contém as classes que tratam dos dispositivos de entrada). Este método retorna um objeto do tipo KeyboardState que possibilita verificarmos o estado atual das teclas do teclado.

KeyboardState estadoTeclado = Keyboard.GetState();

Para verificar se uma tecla está sendo pressionada, usamos o método IsKeyDown, passando como parâmetro a tecla que desejamos verificar. Aqui iremos testar se a seta para a esquerda (Keys.Left) do teclado foi pressionada e alterar a direção atual da nave para a esquerda em caso positivo. Como queremos que a nave sempre fique dentro da tela, verificamos também se sua coordenada X é maior que zero e só alteramos a direção caso isto também seja verdadeiro.

if (estadoTeclado.IsKeyDown(Keys.Left) && coord.X > 0)
{
    direcao = Direcao.Esquerda;
}

Um teste similar é feito para a movimentação para a direita, mas agora precisamos somar a largura da imagem da nave (imagem.Width) à sua coordenada X e verificar se o resultado não é maior que a largura da tela (JogoInvasores.LARGURA). Esta soma é necessária porque queremos testar o ponto mais à direita da nave, ou seja, seu ponto mais à esquerda (definido pela coordenada X) mais sua largura (definido pela largura de sua imagem, ou melhor, imagem.Width).

else if (estadoTeclado.IsKeyDown(Keys.Right)
         && coord.X + imagem.Width < JogoInvasores.LARGURA)
{
    direcao = Direcao.Direita;
}

Se a nave não se movimentou nem para a direita nem para a esquerda (seja porque uma tecla não foi pressionada ou porque ela atingiu os limites da tela), então não há mais nada a se fazer além de definir sua direção como Parado, ou seja, não se move para nenhum dos dois lados.

else
{
    direcao = Direcao.Parado;
}

Recapitulando a definição do tipo enumerado Direcao, nós atribuímos valores às direções Esquerda (-1) e Direita (1), além de Parado (0). Isto será usado agora para mover a nave.
Faremos assim: a nova coordenada X da nave será sua coordenada X atual mais o deltaTempo vezes sua velocidade vezes a direção. Assim, deltaTempo * velocidade gera um valor. Se a direção for Esquerda, a última multiplicação irá gerar um valor negativo, que somado à coordenada da nave irá reduzir sua posição fazendo que ela se mova pra esquerda. Para a direita funciona exatamente ao contrário, deltaTempo * velocidade * 1 (valor definido para Direita) gera um número positivo que faz a nave avançar para a direita da tela. Por fim, Parado vale 0, portanto irá fazer a conta toda resultar em 0 e nenhum movimento será feito.
Lembrando que estamos usando um Vector2 para a coordenada, portanto podemos usar seu operador += que foi sobrecarregado para somar dois vetores. Vamos passar apenas um valor para o X do vetor, mantendo Y como 0, de forma que nenhum movimento será executado neste eixo.

coord += new Vector2(velocidade * jogo. DeltaTempo * (int)direcao, 0);

Para encerrar por hoje, vamos criar uma instância de NaveJogador no jogo e adicioná-lo como um componente.
Antes de qualquer coisa, precisamos carregar a textura usada pela nave, pois sem isso não tem como ela aparecer na tela. No XNA, todo arquivo de imagem deve ser adicionado ao projeto antes de ser usado, pois ele será compilado para um formato próprio do XNA.
Pra começar, copie esta imagem para a pasta Content do seu projeto (pelo Windows, não pelo Visual C#):

Feito isto, vá no seu projeto e clique com o botão direito em Content. Dali escolha Add -> Existing Item. Veja que o diálogo que se abriu já filtra o tipo de arquivo como “Content Pipeline Files”, ou seja, ele já filtrou todos os arquivos que o XNA é capaz de processar. Escolha o arquivo imagemJogador.png e clique em Add. Pronto, esta imagem já faz parte do projeto e já podemos carregá-la no código.
Para carregar a imagem, vamos usar o método LoadContent da classe JogoInvasor, mas primeiro defina um atributo do tipo Texture2D nesta classe.

private Texture2D imagemJogador;

Agora sim, em LoadContent usamos Content.Load para carregar o arquivo desejado. Este método é usado para carregar diversos tipos de arquivo, então especificamos que estaremos carregando uma textura. O nome do arquivo carregado vem entre parênteses e não deve ser acompanhado da extensão.

imagemJogador = Content.Load< Texture2D >("imagemJogador");

Se tudo foi feito da maneira correta, podemos criar um atributo que irá conter a NaveJogador e instanciá-lo:

private NaveJogador naveJogador;

A nave do jogador será instanciada num momento oportuno de acordo com o estado do jogo, mas para encerrar por aqui vamos instanciar o jogador dentro do método Update apenas para vê-lo em ação.
Queremos criar uma nave só, então primeiramente verificamos se o atributo naveJogador é nulo. Se for, significa que ainda não criamos nenhuma nave, portanto vamos criá-la. Se não for nulo, uma nave já foi criada e nada mais precisa ser feito.
Para criar a nave, instanciamos um objeto normalmente chamando seu construtor. Os parâmetros são this (referência ao jogo que criou a nave), imagemJogador (a imagem da nave) e new Vector2(300, 460) (as coordenadas iniciais da nave).
Uma vez instanciada, adicionamos a nave aos componentes do jogo usando o método Components.Add e passando a nave criada como argumento. Se quisermos remover a nave posteriormente, basta usar Components.Remove e novamente passar a nave desejada como argumento.

if (naveJogador == null)
{
    naveJogador = new NaveJogador(this, imagemJogador, new Vector2(300, 460));
      Components.Add(naveJogador);
}

Terminamos aqui mais uma parte do tutorial. Agora já temos uma nave que se move para a esquerda e a direita usando as setas do teclado. Na próxima parte, veremos como fazer a nave atirar. Até lá.
Como sempre, o projeto atualizado pode ser baixado aqui.

Projeto em execução. Clique para ampliar.

7 comentários em “Tutorial: XNA Invasores – Parte 6”

  1. Oi Jonas, o XNA trata automaticamente o magenta (RGB 255, 0, 255) como uma máscara de transparência.
    Quando você desenha qualquer imagem que tenha esta cor, ele vai ignorar os pixels magenta deixando o fundo transparente.

  2. Diego,
    Parabéns pela ótima série de artigos. Sua didática é excelente! Vamos aguardar a parte 7. 🙂

  3. Jonas,
    Vc quer mudar a cor de fundo do jogo???
    Se for, é só achar o método Draw em JogoInvasores
    protected override void Draw(GameTime gameTime)
    {
    graphics.GraphicsDevice.Clear(Color.Black); //aqui onde vc escolhe a cor de fundo.
    base.Draw(gameTime);
    }
    Espero ter ajudado.

  4. Guilherme Cruz

    faltou somente avisar que a clase NaveJogador faltou importar isso aqui ó:
    using System;
    using System.Collections.Generic;
    using System.Text;
    xD
    ou então eu pulei esse pedaço!
    awuehawuheuawe
    mas o tutorial tá Perfet! os arquivos exemplos são excelentes!
    parabéns! Eternamente grato!

Não é possível comentar.