XNA Gamefest 2008 - Introdução da cobertura do evento

Já estou de volta a Varginha e decidi começar com o relato da cobertura do XNA Gamefest 2008, o meu primeiro evento que participei e que valeu a pena. Primeiro que teve uma quantidade enorme de pessoas (segundo dados não oficiais, entre 1700 e 2 mil pessoas), auditórios e palestras lotadas (a de Game Design com o Roger Tavares deve ter tido pelo menos umas 200 pessoas. Eu também não imaginaria uma quantidade dessa de pessoas numa palestra de um assunto que a maioria dos desenvolvedores continua ignorando em fóruns, que é o game design), muitos prêmios simples distribuídos por sorteio (pena não ter ganhado nada…), Xbox 360 com vários games pra jogar (joguei Guitar Hero e vi que preciso treinar muito antes de tentar jogar de verdade), ter visto várias pessoas conhecidas do meio ao vivo (como o chefão da Ubisoft, que ministrou uma conferência impressionante, o Roger Tavares que tem muito carisma e o Alex Oliver, que deu uma aula de anatomia que me fez começar a repensar o meu curso de desenho…), além de ter conhecido pessoalmente alguns usuários de fóruns de discussão, como o Pérsio Flexa, os amigos dele, o Eric Draven (moderador da UniDev) e a Alissa Bates.

Bom, como o evento foi um despejo enorme de conhecimento na minha mente, decidi fracionar a cobertura do evento em vários posts para não ter um texto gigante e para ir comentando aos poucos. Eu não vou apenas comentar sobre como foi o evento. Vou tentar também, dependendo da minha disponibilidade, comentar sobre os assuntos propostos em posts, com fotos dos slides.

Por fim, fique com algumas fotos do evento (abaixo). Mandarei mais fotos depois, junto com outros artigos. De qualquer jeito, parabéns aos organizadores e a Microsoft por ter bancado um evento desse porte. Espero que tenha mais eventos desse, já que só num evento assim pra reunir pessoas que tem o mesmo interesse que você, discutindo assuntos técnicos sem medo de ver uma fisionomia “não entendi nada do que você está comentando…”, comum quando você tenta comentar sobre isso com pessoas de fora da área.

Explicando algumas fotos: a fitinha amarela serviu para que a gente pudesse circular no evento (alguns locais tinham seguranças e caso a fitinha se perdesse, você teria de arrumar outro quilo de alimento. É como se fosse um comprovante de ingresso); na segunda foto eu sou o cara de óculos (o outro é o Pérsio Flexa) e as outras fotos são das palestras e de alguns locais.



Tutorial: XNA Invasores - Parte 9

Como prometido na última parte do tutorial, hoje vamos começar a implementar as colisões entre os objetos do jogo. Antes de fazer, vamos primeiro organizar melhor a classe JogoInvasores, implantando um sistema de estados para controlar o fluxo do jogo.

Vamos criar um novo arquivo com o nome EstadoJogo.cs e dentro dele adicionar o seguinte código:

namespace Tutorial_XNAInvasores
{
    public enum EstadoJogo
    {
        Menu, TelaDeJogo, GameOver, Encerrar, Invasao, Vitoria
    }
}

Este arquivo irá definir os possíveis estados do jogo: Menu (o jogo está apresentando o menu principal), TelaDeJogo (a tela principal do jogo), GameOver (o jogador perdeu), Encerrar ( o jogador pediu para sair do jogo), Invasao (as naves invadiram a área do jogador) e Vitoria (o jogador venceu).

Para começar a usar estes estados no jogo, primeiro é necessário declarar um atributo em JogoInvasores para armazenar o estado atual:

private EstadoJogo estado;

Feito isso, definimos o estado inicial do jogo no método Initialize.

Estado = EstadoJogo.Menu;

Repare que não estamos definindo o estado diretamente através do atributo, mas sim estamos usando uma propriedade “Estado”. Fazemos isso por que queremos que alguns métodos sejam chamados sempre que houver uma mudança de estado.

A propriedade “Estado” basicamente altera o estado e executa alguma ação extra dependendo do estado anterior.

private EstadoJogo Estado
{
    get { return estado; }
    set
    {
        switch (value)
        {
            case EstadoJogo.Menu:
                estado = value;
                break;
            case EstadoJogo.TelaDeJogo:
                estado = value;
                IniciarJogo();
                break;
            case EstadoJogo.GameOver:
                if (estado == EstadoJogo.TelaDeJogo)
                {
                    FinalizarJogo();
                }
                estado = value;
                break;
            case EstadoJogo.Invasao:
                if (estado == EstadoJogo.TelaDeJogo)
                {
                    FinalizarJogo();
                }
                estado = value;
                break;
            case EstadoJogo.Vitoria:
                if (estado == EstadoJogo.TelaDeJogo)
                {
                    FinalizarJogo();
                }
                estado = value;
                break;
            case EstadoJogo.Encerrar:
                estado = value;
                break;
        }
    }
}

Conforme você já deve ter reparado, esta propriedade faz chamadas a dois métodos que ainda não foram criados: IniciarJogo e FinalizarJogo. Estes são os métodos responsáveis por iniciar e finalizar a parte central do jogo.

Vamos então ver como fica IniciarJogo. Lembram que a gente adicionou os componentes NaveJogador e ControladorDeNaves dentro do método Update de JogoInvasor? Pois é, isto agora será feito por IniciarJogo, então podemos remover aquele código que marcamos como temporário. Para quem não se lembra, o código a ser removido é este:

    // Temporário: Cria e adiciona uma NaveJogador ao jogo
    if (naveJogador == null)
    {
        naveJogador = new NaveJogador(this, imagemJogador,
            new Vector2(300, 460));
        Components.Add(naveJogador);
    }

    // Temporário: Cria e adiciona um ControladorDeNaves ao jogo
    if (controladorDeNaves == null)
    {
        controladorDeNaves = new ControladorDeNaves(this);
        Components.Add(controladorDeNaves);
    }

IniciarJogo vai fazer praticamente a mesma coisa, com a diferença que não será preciso ficar verificando se o atributo é nulo (já que iremos chamar este método uma vez só, ao contrário de Update que era chamado várias vezes por segundo) e mais pra frente vamos instanciar também os escudos usando este método.

private void IniciarJogo()
{
    naveJogador = new NaveJogador(this, imagemJogador, new Vector2(300, 460));
    Components.Add(naveJogador);
    controladorDeNaves = new ControladorDeNaves(this);
    Components.Add(controladorDeNaves);
}

Para encerrar o jogo usamos o método FinalizarJogo. Nele, armazenamos a pontuação do jogador e depois removemos os componentes do jogo. A remoção dos componentes é uma tarefa muito simples, basta passar uma instância do componente para o método Components.Remove.

Novamente, neste método iremos remover também os escudos futuramente, mas por enquanto fica assim:

private void FinalizarJogo()
{
    ultimaPontuacao = naveJogador.Pontos;
    Components.Remove(naveJogador);
    naveJogador = null;
    Components.Remove(controladorDeNaves);
    controladorDeNaves = null;
}

O atributo ultimaPontuacao serve para armazenar a pontuação do jogador, mesmo depois que o componente for destruído. Ele é declarado assim:

private int ultimaPontuacao;

Agora voltamos ao método Update onde iremos definir o comportamento do jogo de acordo com seu estado atual. São três comportamentos distintos:

  • Se o estado for TelaDeJogo, chamamos o método AtualizarTelaDeJogo que contém a lógica central do jogo.
  • Se o estado for encerrar, finalizamos o jogo com o método Exit, da classe Game.
  • E se o estado for qualquer outro, chamamos o método ChecarOpcao que verifica a opção do usuário.
  • switch (estado)
    {
        case EstadoJogo.Menu:
        case EstadoJogo.GameOver:
        case EstadoJogo.Invasao:
        case EstadoJogo.Vitoria:
            ChecarOpcao();
            break;
        case EstadoJogo.TelaDeJogo:
            AtualizarTelaDeJogo();
            break;
        case EstadoJogo.Encerrar:
            Exit();
            break;
    }
    

    O método ChecarOpcao ficará responsável por verificar se o jogador deseja jogar novamente ou encerrar o jogo, mas nessa parte do tutorial não vamos nos preocupar com isto, portanto vamos apenas fazer com que este método altere o estado para “TelaDeJogo”.

    private void ChecarOpcao()
    {
        Estado = EstadoJogo.TelaDeJogo;
    }
    

    AtualizarTelaDeJogo é um método bem grande que verifica as colisões e checa o fim de jogo. Hoje vamos ver apenas algumas partes deste método. Já que ainda não estamos trabalhando com os escudos, algumas verificações ainda não serão feitas.

    Repare que vamos utilizar uma classe adicional, chamada GerenciadorDeColisao, para testar colisões entre os objetos do jogo. Esta é uma classe que contém apenas métodos estáticos e sua declaração será vista mais abaixo.

    private void AtualizarTelaDeJogo()
    {
    }

    Começamos o método testando se o tiro do jogador não é nulo e depois verificando sua colisão com as naves do ControladorDeNaves.

    if (naveJogador.Tiro != null)
    {
        if (GerenciadorDeColisao.ChecarColisao(controladorDeNaves,
            naveJogador.Tiro))
        {
            naveJogador.Pontos += 100;
            naveJogador.Tiro.Destruir = true;
        }
    }
    

    Depois fazemos o processo inverso, verificando a colisão do tiro das naves com a nave do jogador.

    if (controladorDeNaves.Tiro != null)
    {
        if (GerenciadorDeColisao.ChecarColisao(naveJogador,
            controladorDeNaves.Tiro))
        {
            naveJogador.Vidas--;
            controladorDeNaves.Tiro.Destruir = true;
        }
    }
    

    O resto do método diz respeito ao fim do jogo.

  • Se a quantidade de vidas restantes do jogador for menor que 0, mudamos o estado do jogo para “GameOver”.
  • Se as naves tiverem invadido o espaço do jogador, mudamos o estado para “Invasao”.
  • E se as naves inimigas acabaram, o jogador venceu e mudamos o estado para “Vitoria”.
  • if (naveJogador.Vidas < 0)
    {
        Estado = EstadoJogo.GameOver;
    }
    else if (controladorDeNaves.Invadiu)
    {
        Estado = EstadoJogo.Invasao;
    }
    else if (controladorDeNaves.QuantidadeDeNaves == 0)
    {
        Estado = EstadoJogo.Vitoria;
    }
    

    Por fim, se o jogador apertar ESC a partida atual termina com uma derrota. É claro que esta é uma opção muito simples, um aperfeiçoamento do jogo seria exibir uma tela de pause ou pelo menos de confirmação quando ESC for pressionado.

    if (Keyboard.GetState().IsKeyDown(Keys.Escape))
    {
        Estado = EstadoJogo.GameOver;
    }
    

    Agora já temos quase tudo pronto, falta apenas criar a classe GerenciadorDeColisao. Crie uma nova classe no projeto com este nome:

    namespace Tutorial_XNAInvasores
    {
        class GerenciadorDeColisao
        {
        }
    }
    

    Precisaremos de três versões diferentes do método VerificarColisao. A primeira irá verificar colisão entre as naves do ControladorDeNaves e um Tiro, a segunda irá testar se um Tiro colidiu com a NaveJogador e a última testa colisão entre um retângulo e um Tiro.

    Vamos à implementação do primeiro método:

    public static bool ChecarColisao(ControladorDeNaves controladorDeNaves,
        Tiro tiro)
    {
        NaveInvasor[,] naves = controladorDeNaves.Naves;
        for (int x = 0; x < ControladorDeNaves.NAVES_X; x++)
        {
            for (int y = 0; y < ControladorDeNaves.NAVES_Y; y++)
            {
                if (naves[x, y] != null)
                {
                    if (naves[x, y].ObterRetangulo().Intersects(
                        tiro.ObterRetangulo()))
                    {
                        naves[x, y] = null;
                        controladorDeNaves.QuantidadeDeNaves--;
                        return true;
                    }
                }
            }
        }
         return false;
    }
    

    Aqui estamos percorrendo todas as naves do ControladorDeNaves e verificando se elas não são nulas, ou seja, se já não foram destruídas. Caso não sejam, utilizamos o método Intersects da classe Rectangle para verificar se os retângulos da nave e do tiro se sobrepõem. Se isto acontecer, destruímos a nave em questão, reduzimos a contagem de naves restantes no controlador, e retornamos verdadeiro para que o jogo saiba que o tiro deve ser destruído e pontos devem ser adicionados ao total do jogador.

    A segunda implementação deste método verifica a colisão do Tiro dos invasores com a NaveJogador:

    public static bool ChecarColisao(NaveJogador naveJogador, Tiro tiro)
    {
        if (tiro.ObterRetangulo().Intersects(naveJogador.ObterRetangulo()))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    

    Temos um método bem mais simples aqui. Como há apenas uma NaveInvasor e um Tiro pra testar, não há necessidade de iterar por nenhuma lista. Apenas testamos então a interseção entre os retângulos dos dois objetos.

    Por fim, mais simples ainda, o teste que será utilizado futuramente para a colisão com os escudos:

    public static bool ChecarColisao(Rectangle caixa, Tiro tiro)
    {
        if (caixa.Intersects(tiro.ObterRetangulo()))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    

    Repare que o funcionamento é basicamente o mesmo da segunda versão, com a diferença que aqui recebemos diretamente uma instância de Rectangle, ao invés de obtê-lo dentro do método. Isto foi feito só para demonstrar algumas formas diferentes de trabalhar com a colisão.

    Para fechar, precisamos apenas adicionar umas propriedades em ControladorDeNaves para que seja possível acessar seus atributos “tiro”, “invadiu”, “quantidadeDeNaves” e “naves”. Feito isso, já podemos compilar e rodar o jogo.

    public Tiro Tiro
    {
        get { return tiro; }
    }
    
    public bool Invadiu
    {
        get { return invadiu; }
    }
    
    public int QuantidadeDeNaves
    {
        get { return quantidadeDeNaves; }
        set { quantidadeDeNaves = value; }
    }
    
    public NaveInvasor[,] Naves
    {
        get { return naves; }
    }
    

    Veja que agora você já pode destruir as naves inimigas e qualquer evento que leve ao fim do jogo (como destruir todos os inimigos ou pressionar ESC) simplesmente faz com que ele reinicie. É claro que vamos mudar isso em breve. Até a próxima.

    Não deixem de conferir o projeto completo aqui.

    Tutorial: XNA Invasores - Parte 8

    Hora de implementar as naves inimigas para que a gente possa treinar um pouco a pontaria. Vamos começar adicionando uma nova classe chamada NaveInvasor que herda da classe Nave, feita nas partes anteriores do tutorial.

    Esta classe é bem simples. Precisamos apenas criar um construtor onde definiremos sua direção e velocidade inicial, e sobrescrever o método Update para dizermos como a nave deve ser atualizada.

    Começamos com o construtor. Simplesmente definimos a direção inicial como Direita (o que significa que inicialmente todas as naves estarão se deslocando para a direita) e a velocidade de deslocamento como 20 (ou seja, as naves irão se mover 20 pixels por segundo na direção atual).

    public NaveInvasor(JogoInvasores jogo, Texture2D imagem, Vector2 coord)
        : base(jogo, imagem, coord)
    {
        direcao = Direcao.Direita;
        velocidade = 20.0f;
    }

    O método Update irá mover a nave na direção desejada, da mesma forma que foi feito com NaveJogador (na verdade, esta parte é igual nas duas classes e deveria ter aparecido na classe Nave, mas como isto só foi visto agora, vamos deixar assim mesmo).

    public override void Update(GameTime gameTime)
    {
        coord += new Vector2(velocidade * jogo.DeltaTempo * (int)direcao, 0);
        base.Update(gameTime);
    }

    Para finalizar esta classe, vamos criar um método que deve ser chamado quando uma das naves atinge um dos cantos da tela. Quando isto ocorre, todas as naves se deslocam um pouco para baixo e invertem sua direção.

    public void InverterDirecao()
    {
        coord += new Vector2(0, 10);
        if (direcao == Direcao.Direita)
        {
            direcao = Direcao.Esquerda;
        }
        else if (direcao == Direcao.Esquerda)
        {
            direcao = Direcao.Direita;
        }
    }

    Agora, para adicionar as naves invasoras no jogo, vamos criar um controlador que será responsável por criar, atualizar e destruir estas naves. Adicione uma nova classe no projeto com o nome de ControladorDeNaves e faça com que ela herde de DrawableGameComponent. Faremos isto, pois ControladorDeNaves é que será adicionado como um componente ao jogo, ao invés de adicionarmos cada NaveInvasor individualmente. Isto ajuda em obter um melhor desempenho, uma vez que o XNA não precisará cuidar de dezenas de componentes.

    Criada a classe, adicionamos seus atributos. Como sempre, começamos com uma instância de JogoInvasores:

    private JogoInvasores jogo;

    Em seguida criamos uma matriz que irá conter as naves criadas pelo controlador. Para quem não está muito familiarizado com o C#, é possível fazer a declaração de uma matriz bidimensional sem especificar o tamanho de nenhuma de suas dimensões escrevendo “[,]” após o tipo da matriz. Faremos isto, pois os limites da matriz serão definidos mais à frente, quando criarmos as naves.

    private NaveInvasor[,] naves;

    Duas texturas são necessárias para NaveInvasor: a textura da própria nave e a textura do tiro da nave. Para armazenar estas texturas, criamos mais dois atributos no controlador:

    private Texture2D imagemNave;
    
    private Texture2D imagemTiro;

    O controlador de naves será responsável por verificar se uma nave atingiu os limites da tela e ordenar a todas elas que invertam sua direção. Para fazer este controle, utilizamos um atributo que diz se as naves devem ou não inverter sua direção.

    private bool inverterDirecao;

    Utilizamos uma variável para contar o tempo que falta para que as naves possam atirar, impedindo que elas fiquem atirando direto, sem nenhum intervalo.

    private float tempoParaAtirar;

    Quando for permitido que as naves atirem (ou seja, o tempo para atirar tiver passado) faremos um teste baseado num número aleatório para definir qual nave irá atirar (veremos isto mais abaixo). Para gerar números aleatório, vamos criar uma instância da classe Random provida pelo .Net Framework justamente para lidar com números randômicos.

    private Random random;

    Finalizando a parte dos tiros, quando um for disparado precisaremos armazenar uma nova instância de Tiro, então criaremos um atributo para isto. Assim como foi feito com NaveJogador, apenas um tiro por vez poderá separado, portanto não há necessidade de se criar um vetor ou uma lista, apenas uma variável simples já basta.

    private Tiro tiro;

    Durante sua execução, o Jogo pode acabar de várias formas, mas apenas duas delas estão diretamente relacionadas com as naves invasoras: todas as naves foram destruídas (vitória do jogador) ou as naves ultrapassaram um limite e invadiram a defesa (derrota do jogador). Para controlar isto, teremos um atributo que conta a quantidade de naves restantes no Jogo e um outro que indica se alguma nave conseguiu invadir a área do jogador.

    private int quantidadeDeNaves;
    
    private bool invadiu;

    Para terminar os atributos de ControladorDeNaves, vamos adicionar duas constantes que irá definir a quantidade de naves no eixo X e Y.

    public const int NAVES_X = 10;
    
    public const int NAVES_Y = 5;

    Agora criamos o construtor. Primeiramente armazenamos a instância de JogoInvasores, definimos o atraso inicial do primeiro tiro (tempoParaAtirar), instanciamos o gerador de números aleatórios e definimos tiro como null (isto é importante, pois um dos requisitos para a criação de um novo tiro é que este atributo seja nulo, o que significa que não existe outro tiro na tela disparado por uma nave invasora).

    public ControladorDeNaves(JogoInvasores jogo)
        : base(jogo)
    {
        this.jogo = jogo;
    
        tempoParaAtirar = 2.0f;
        random = new Random();
        tiro = null;

    E depois finalizamos definindo a quantidade inicial de naves como sendo NAVES_X * NAVES_Y, ou seja, cinqüenta naves, e dizendo ao jogo que a invasão ainda não ocorreu (invadiu = false).

    quantidadeDeNaves = NAVES_X * NAVES_Y;
    invadiu = false;

    Continuando, vamos carregar as texturas da nave e do tiro sobrescrevendo o método LoadContent do ControladorDeNaves. Perceba que desta vez vamos carregar as texturas diretamente no componente, ao invés de carregá-lo em JogoInvasores e passar para o ControladorDeNaves como parâmetro. Fizemos isto porque o jogo terá um único controlador de naves, o que significa que uma mesma textura não será carregada várias vezes, portanto podemos deixar que o próprio controlador carregue sua textura.

    Uma vez que a textura da nave já foi carregada, podemos então instanciar as naves do jogo. Para isso, vamos fazer uma chamada ao método CriarNaves dentro de LoadContent, fazendo com que as naves sejam criadas imediatamente após o carregamento das texturas.

    protected override void LoadContent()
    {
        imagemNave = jogo.Content.Load("imagemNaveInvasor");
        imagemTiro = jogo.Content.Load("imagemTiroJogador");
        CriarNaves();
    
        base.LoadContent();
    }

    Ok, estamos carregando as imagems, mas para que elas possam realmente ser carregadas precisamos adicioná-las ao projeto. Vá em Add -> Existing Item… e adicione estes dois arquivos ao seu projeto: imagemTiroInvasor.png e imagemNaveInvasor.

    A implementação do método CriarNaves fica da seguinte forma: alocamos memória para a matriz que tinha sido declarada anteriormente e depois fazemos um laço por todos os elementos desta matriz criando novas instâncias de NaveInvasor, passando suas coordenadas e a imagem.

    O método Update é bem grandinho, então vamos por partes para não confundir. Primeiramente, atualizamos o tempoParaAtirar diminuindo seu valor de acordo com o deltaTempo.

    public override void Update(GameTime gameTime)
    {
        tempoParaAtirar -= jogo.DeltaTempo;

    Depois, fazemos um laço por todas as naves do controlador usando a palavra-chave do C# “foreach”. Dentro deste laço vamos manipular cada uma das naves contidas na matriz naves.

    Antes de mais nada, verificamos se a nave não é nula. Caso seja nula, nada é feito. Caso não seja, chamamos o método Update da nave atual.

    if (nave != null)
    {
        nave.Update(gameTime);

    Agora verificamos se a nave chegou perto demais de um dos lados da tela e ativar o flag inverterDirecao caso isto ocorra. No nosso caso, consideramos que uma nave chegou perto demais de um dos lados se ela estiver a menos de 10 pixels do canto da tela.

    if (nave.Coord.X < 10 && nave.Direcao == Direcao.Esquerda)
    {
        inverterDirecao = true;
    }
    else if (nave.Coord.X + imagemNave.Width > JogoInvasores.LARGURA - 10
             && nave.Direcao == Direcao.Direita)
    {
        inverterDirecao = true;
    }

    Na sequencia verificamos se a nave invadiu o espaço do jogador, o que aqui significa ter sua coordenada Y maior que 340.

    if (nave.Coord.Y > 340)
    {
        invadiu = true;
    }

    Para finalizar este laço, testamos se o tempo para atirar já passou e se não há nenhum outro tiro na tela (tiro == null). Em caso positivo, geramos um número aleatório entre 0 e 1000 e verificamos se ele é menor que 30. Se for, um novo tiro é criado logo abaixo da nave atual. Este teste com o número aleatório existe para permitir que todas as naves atirem. Quando um novo tiro é criado, não podemos esquecer de reinciar o contador tempoParaAtirar.

    if (tempoParaAtirar < 0.0f && tiro == null)
    {
        if (random.Next(1000) < 30)
        {
            tiro = new Tiro(jogo, imagemTiro, new Vector2(
                nave.Coord.X + imagemTiro.Width / 2, nave.Coord.Y),
                200.0f);
            tempoParaAtirar = 2.0f;
         }
    }

    Antes de prosseguir, vamos fechar dois blocos de código que ainda estão abertos.

        }
    }

    Fechamos estes blocos mas ainda há coisas a serem feitas em Update. Primeiramente, caso o flag inverter direção esteja ativo, vamos fazer uma nova iteração por todas as naves chamando seu método inverterDirecao. Estamos fazendo este novo laço ao invés de fazê-lo dentro do laço anterior para garantir que todas as naves tenham sua direção invertida. Caso isso fosse feito diretamente no outro laço, as naves atualizadas antes da ativação do flag inverterDirecao não seriam afetadas pelo flag.

    if (inverterDirecao)
    {
        foreach (NaveInvasor nave in naves)
        {
            if (nave != null)
            {
                nave.InverterDirecao();
            }
        }
            inverterDirecao = false;
    }

    Por fim, caso um tiro tenha sido disparado, ele é atualizado. Se o tiro tiver ativado seu flag destruir (ao sair da tela ou atingir um objeto) atribuímos null a ele e o removemos da memória.

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

    Terminamos o método Update chamando base.Update e fechando as últimas chaves abertas.

    base.Update(gameTime);
    }

    O método Draw é muito simples. Primeiramente iteramos por todas as naves e verificamos se a nave corrente é nula, e caso não seja, a desenhamos. Depois terminamos o método desenhando o tiro caso ele não seja nulo.

    public override void Draw(GameTime gameTime)
    {
        foreach (NaveInvasor nave in naves)
        {
            if (nave != null)
            {
                nave.Draw(gameTime);
            }
        }
    
        if (tiro != null)
        {
            tiro.Draw(gameTime);
        }
    
        base.Update(gameTime);
    }

    Para adicionar o controlador de naves ao jogo e permitir que as naves apareçam na tela, vamos usar o mesmo processo que fizemos antes com NaveJogador. Iremos criar uma variável controladorDeNaves e instanciá-lo temporariamente no método Update de JogoInvasores. Mais à frente quando implementarmos o sistema de estados do Jogo, a criação de controladorDeNaves irá aparecer em um lugar mais conviniente.

    Declare a variável dentro da classe JogoInvasores da seguinte forma:

    private ControladorDeNaves controladorDeNaves;

    Depois, adicione o seguinte trecho de código no método Update.

    // Temporário: Cria e adiciona um ControladorDeNaves ao jogo
    if (controladorDeNaves == null)
    {
        controladorDeNaves = new ControladorDeNaves(this);
        Components.Add(controladorDeNaves);
    }

    Agora já temos as naves inimigas voando pelo cenário e atirando contra o jogador. Mas por hora nenhuma colisão está sendo feita e os tiros irão simplesmente passar direto pelos personagens. Pior que isso, nenhuma verificação está sendo feita para determinar se o jogo acabou, portanto se você deixar o jogo rodando por um tempo as naves eventualmente irão sair pela parte de baixo da tela.

    Nas próximas partes tanto os cálculos de colisão quanto as verificação de fim de jogo serão adicionadas. Enquanto isso, vocês podem testar o jogo baixando o projeto aqui.

    Até mais.

    Jogo com as Naves Invasoras!

    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).

    Continue lendo »

    Preview da versão 3.0 do XNA

    Foi lançado ontem o Community Technical Preview do XNA Game Studio 3.0.

    As novidades desta nova versão incluem suporte ao Visual Studio 2008 e a possibilidade de se rodar os jogos no Zune. Além disso, há melhorias na API do Framework no suporte a multimídia e efeitos sonoros.

    O suporte ao XBOX 360 foi removido desta versão de testes e estará de volta na versão final. Talvez por isso mesmo, o tamanho do download caiu de 98.6 MB da versão 2.0 para 41.7 MB no 3.0 (veremos se o motivo da redução é esse mesmo quando a versão final sair).

    Para fazer o download e saber mais detalhes, clique aqui.

    Fonte: Meiobit Games.

    Agradecimentos ao Rodrigo pela dica.

    Tutorial: XNA Invasores - Parte 6

    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.

    Continue lendo »

    Tutorial: XNA Invasores - Parte 5

    Na 5º parte do nosso tutorial vamos ver o funcionamento dos Game Components do XNA, uma adição simples mas que ajuda muito na reutilização de código.

    Antes de começar pra valer, vamos ver uma coisinha que ficou faltando na última parte mas é muito importante: a taxa de quadros por segundo do jogo. O XNA tem duas formas de lidar com a velocidade do jogo, temos uma taxa fixa ou variável.

    Por padrão, o jogo sempre trabalha com uma taxa fixa de 60 quadros por segundo, ou qualquer outro valor definido através da propriedade TargetElapsedTime. Isto é muito interessante quando se faz jogos pro XBOX 360, já que como o hardware é padrão, temos a garantia de que o jogo irá se comportar da mesma forma em todos os videogames.

    Continue lendo »

    Palestras de XNA da Game Developers Conference para download

    Para aqueles que estão estudando o XNA ou querem apenas dar uma olhada, a Microsoft colocou para download muitas palestras que ocorreram na Game Developers Conference (no começo do ano). Para quem quiser baixar, vá no link abaixo, que tem todos os links e explicações de cada palestra:

    GDC 2008 Presentations

    O ruim é que a maioria são palestras realmente grandes e só podem ser abertas no Office 2007 ¬¬ Para quem não tem o Office 2007, pode tentar atualizar as versões mais antigas (2007, XP e 2003) para que elas possam abrir os arquivos. Para mais informações, visite este post do WinAjuda (aliás, devo testar essa tática amanhã).

    Tutorial: XNA Invasores - Parte 4

    Logo do XNAHoje vamos começar a implementar o jogo usando o XNA, mas primeiro vamos ver algumas particularidades que diferem o desenvolvimento com XNA do desenvolvimento com outras bibliotecas.

    Uma grande diferença do XNA em relação ao que estamos acostumados com outras bibliotecas (como o Allegro ou SDL) é seu modelo de aplicação. Normalmente, quando criamos um jogo ou qualquer outra aplicação que tenha repetição, devemos criar um laço principal onde a lógica central será executada repetidamente até que uma determinada situação ocorra e o programa seja encerrado.

    Continue lendo »

    Devil May Cry 3: Uma lição em Character Design

    Não sei exatamente o porque, mas de uma hora pra outra resolvi jogar o Devil May Cry 3.

    Ele, em essência, não passa da mistura de um conjunto de idéias de outros jogos da produtora Capcom, com uma roupagem diferente.

    O jogo é, em essência, um beat’em up como o Final Fight, mas num ambiente soturno que lembra muito o famoso Demon’s Crest, com arquitetura gótica e inimigos demoníacos. O sistema de habilidades foi claramente baseado no Mega Man, sendo pouquíssimas delas obtidas sem a derrota de um chefe. Além disso, as telas de save, o estilo dos quebra-cabeças e o ambiente da cidade destruída vão te dar aquela sensação de Resident Evil por um bom tempo, principalmente quando você começar a zerar o jogo para colecionar os extras: imagens, vídeos, modos de jogo e novas roupas para os personagens.

    “Ah ta. Esse jogo é uma mistureba de um monte de outros jogos. Então por que você quis fazer essa análise?”. Fácil, fácil: o character design.

    Continue lendo »