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

Tutorial: XNA Invasores – Parte 7

Logo do XNADe volta com o tutorial XNA Invasores e é hora de adicionar um pouco de ação ao jogo. Hoje vamos dar poder de fogo à nossa nave.
Iniciamos com a criação da classe Tiro que irá representar os tiros disparados pelo Jogador (e futuramente pelas naves inimigas). Como sempre, criamos uma nova classe (Add -> Class…) e damos a ela o nome de Tiro. Como esta classe também é um Drawable Game Component, fazemos com que ela herde de Entidade (assim com foi feito com a classe Nave).

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Tutorial_XNAInvasores
{
    class Tiro : Entidade
    {
    }
}

Tiro tem, além dos atributos herdados de Entidade, uma velocidade e um flag “destruir” que indica se o Tiro deve ser destruído pelo Jogo. Além disso temos uma referência para a textura que será usada para desenhar o Tiro.

private float velocidade;
private bool destruir;
private Texture2D imagem;

O construtor do Tiro é bem simples. Basicamente recebemos suas coordenadas e velocidade, além da imagem previamente carregada e a referência para o Jogo (como já vinha sendo feito com outras classes). Por fim, é necessário apenas definir que o Tiro ainda não deve ser destruído, mudando seu flag “destruir” para false.

public Tiro(JogoInvasores jogo, Texture2D imagem, Vector2 coord,
    float velocidade) : base(jogo)
{
      this.imagem = imagem;
      this.coord = coord;
      this.velocidade = velocidade;
      destruir = false;
}

No método Update o Tiro será atualizado. Aqui, sua coordenada é alterada somando a velocidade (multiplicada pelo deltaTempo) à sua coordenada Y atual. Desta forma, caso a velocidade seja negativa, o Tiro se move para cima e se a velocidade for positiva, o tiro vai para baixo.
Como não faz sentido manter um Tiro na memória se ele tiver saído da tela e não puder mais interferir no Jogo, verificamos suas coordenadas para ver se ele está abaixo ou acima da tela. Quando não estiver mais aparecendo, alteramos seu flag “destruir” para true e a classe que criou o Tiro deverá se encarregar de destruí-lo.

public override void Update(GameTime gameTime)
{
    coord += new Vector2(0, velocidade * jogo.DeltaTempo);
    if (imagem != null)
    {
        if (coord.Y + imagem.Height < 0
            || coord.Y > JogoInvasores.ALTURA)
        {
            destruir = true;
        }
    }
    base.Update(gameTime);
}

O método de desenho segue a mesma lógica do que foi feito com Nave: verificamos se a imagem não é nula, preparamos o SpriteBatch para desenho, desenhamos a textura com as coordenadas do Tiro e finalizamos o SpriteBatch.

public override void Draw(GameTime gameTime)
{
    if (imagem != null)
    {
        jogo.SpriteBatch.Begin();
        jogo.SpriteBatch.Draw(imagem, coord, Color.White);
        jogo.SpriteBatch.End();
    }
    base.Draw(gameTime);
}

Para terminar a classe, criamos uma propriedade de acesso ao atributo “destruir” e um método que retorna um retângulo com as coordenadas e as dimensões do Tiro para cálculo de colisão.

public bool Destruir
{
    get { return destruir; }
    set { destruir = value; }
}
public Rectangle ObterRetangulo()
{
    return new Rectangle((int)coord.X, (int)coord.Y,
        imagem.Width, imagem.Height);
}

Terminada esta etapa, podemos adicionar a seguinte textura ao projeto (da mesma forma que foi feito anteriormente com a textura da nave do jogador). Adicione esta imagem ao projeto ou use uma outra imagem que desejar.
Para carregar a textura no Jogo, podemos usar um recurso interessante dos Game Components que é a possibilidade de um Game Component gerenciar seus próprios recursos. Quando criamos a classe NaveJogador, sua textura foi carregada em JogoInvasores, mas isto também poderia ter sido feito diretamente em NaveJogador. Agora vamos usar este recurso para carregar a textura do Tiro.
Em NaveJogador, sobrecarregamos o método LoadContent (definido por DrawableGameComponent) e carregamos a textura do Tiro nele. Antes, porém, precisamos definir um atributo nesta mesma classe para armazenar a textura carregada.

private Texture2D imagemTiro;
protected override void LoadContent()
{
    imagemTiro = jogo.Content.Load< Texture2D >("imagemTiroJogador");
    base.LoadContent();
}

Agora precisamos criar um atributo para conter o Tiro disparado pelo Jogador. No modelo que estamos usando, o Jogador será capaz de disparar apenas um Tiro por vez, então uma variável simples já é suficiente. Caso queira que ele possa disparar mais Tiros, será necessário criar um vetor. Aproveitamos para adicionar uma propriedade de acesso a este atributo.

private Tiro tiro;
public Tiro Tiro
{
    get { return tiro; }
}

Os Tiros serão disparados quando o Jogador pressionar a barra de espaço e nenhum outro Tiro do Jogador estiver na tela (mantendo o limite de um Tiro por vez). Esta verificação é feita no método Update de NaveJogador.
Neste método primeiramente precisamos verificar se a barra de espaço foi pressionada (estadoTeclado.IsKeyDown(Keys.Space)). Uma vez que esta tecla tenha sido apertada, precisamos ver se o Jogador pode atirar (usando o flag podeAtirar). Durante o jogo, este flag estará com valor “false” sempre que um tiro disparado pelo Jogador estiver na tela.
Podendo atirar, vamos criar uma nova instância de Tiro logo acima da nave do jogador (com velocidade negativa para que ela se mova para cima) e definir o flag “podeAtirar” como false.

public override void Update(GameTime gameTime)
{
    // Código das partes anteriores
    if (estadoTeclado.IsKeyDown(Keys.Space))
    {
        if (podeAtirar)
        {
            podeAtirar = false;
            tiro = new Tiro(jogo, imagemTiro,
                new Vector2(coord.X + imagem.Width / 2, coord.Y), -300.0f);
        }
    }
    base.Update(gameTime);
}

Para finalizar o método Update, é necessário atualizar o Tiro. Lembre-se que o Tiro é um DrawableGameComponent, mas ele não foi adicionado ao Jogo usando o método Components.Add. Isto não foi feito pois nós vamos manipulá-lo diretamente, chamando seus métodos Update e Draw.
Ainda em Update de NaveJogador, verificamos se o Tiro não é nulo e chamamos seu método Update. Após isso, verificamos seu flag “destruir” e o destruímos caso o valor seja true. Por fim, caso o Tiro seja nulo, o flag “podeAtirar” passa a ter valor true, ou seja, na próxima iteração o Jogador será capaz de atirar novamente.

if (tiro != null)
{
    tiro.Update(gameTime);
    if (tiro.Destruir)
    {
        tiro = null;
    }
}
else
{
    podeAtirar = true;
}

Como eu já disse, temos que chamar o método Draw do Tiro manualmente, então vamos fazer isto no Draw de NaveJogador. Aqui é bem simples, basta verificar se o Tiro não é nulo e desenhá-lo a seguir.

public override void Draw(GameTime gameTime)
{
    if (tiro != null)
    {
        tiro.Draw(gameTime);
    }
    base.Draw(gameTime);
}

E assim chegamos ao fim de mais uma parte do tutorial. Para testar os Tiros, basta pressionar a barra de espaço com o Jogo rodando (repare que você só será capaz de disparar um novo Tiro quando o anterior tiver saído da tela).
O projeto completo pode ser baixado aqui.

Jogo

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

  1. Olá Diego,
    Esté ocorrendo um erro nesta linha da classe NaveJogador:
    imagemTiro = content.Load("nave");
    Erro: The name "content" does not exist in the current context.
    Sei que no seu exemplo o C está em maiúsculo, mas o meu coloquei minúsculo mesmo, quando declarei o objeto.
    Agradeço a ajuda!
    Abraço!

  2. É porque na verade Diego esqueceu de deixar a imagem dele no blog, portanto baixe a imagem através do source que ele deixou disponível, foi assim que eu fiz aqui.

  3. Diego, muito obrigado por compartilhar isso com todos, estou empolgado com a oportunidade de criar jogos e tudo mais, sou desenvolvedor .net ha um tempo já…
    Sou meio chato com organização e "limpeza" de código.. em alguns trechos como esse abaixo podem ser "melhorados"…
    if (imagem != null)
    {
    if (coord.Y + imagem.Height < 0 || coord.Y > JogoInvasores.ALTURA)
    […]
    Para:
    if (imagem != null && coord.Y + imagem.Height < 0 || coord.Y > JogoInvasores.ALTURA)
    destruir = true;
    Sei que isso nao muda nada no resultado, mas quando se pega projetos corporativos com classes de 5~6k
    de linhas isso faz uma grande diferença..
    Mais uma vez obrigado por compartilhar seu conhecimento e parabens!
    Abraço.
    Barral.

  4. Ola amigo sou desenvolvedor delphi e c++ a alguns anos e estou acompanhando aqui o jogo.
    Na setima aula, após faze-la toda percebi que o tiro não sai de jeito nenhum, após debugar descobri que o imagemtiro aparecia como nulo, então puxei ele para a linha anterior aonde a variavel tiro recebe novotiro (dentro de UPDATE do navejogador) e então funcionou o tiro.
    Com isto descobri que o loadcontent do navejogador não funciona, sabe me dizer o que pode ser isto ?

Não é possível comentar.