Vamos aprender XNA? – Parte 1 – Back to Basics

Posted on fevereiro 22, 2011 by

16


Olá pessoal, tudo certo?

Aprendi a programar muito cedo. Comecei a brincar com BASIC antes de completar 10 anos de idade. C, Pascal e Assembly vieram logo em seguida.

Colecionava revistas, como a saudosa Micro-Sistemas, onde encontrava mensalmente códigos que transcrevia dezenas de vezes.

Aprendi cedo a gostar de algoritmos gráficos e a detestar as diversificadas interfaces de programação das placas de vídeo da época.

Também dessa época é a minha aversão e paixão por ponteiros, minha predileção por linguagens de nível mais baixo, por registradores, contadores e flags.

Minha motivação? Queria escrever meus próprios jogos.

Infelizmente, nunca havia encontrado tempo ou disposição para fazer algo interessante com as tecnologias que conhecia. Tudo parecia difícil demais, complexo demais ou trabalhoso demais.

Felizmente, para mudar essa história, surgiu o XNA. Essa nova série trata dessa tecnologia.

O que é XNA?

XNA é um framework para desenvolvimento de jogos desenvolvido pela Microsoft que, supostamente, torna a criação de jogos(para PC, Xbox 360 e Windows Phone 7)  muito mais fácil (ou menos difícil).

Na versão 4.0, suporta a criação de jogos para PC, Xbox 360 e Windows Phone 7.

Requisitos de sistema

Nessa série usaremos o XNA Game Studio 4.0, que é uma extensão do Visual Studio para desenvolvimento de games com XNA.

XNA Game Studio 4.0 utiliza o XNA Framework 4.0. É suportado por todas as versões do Visual Studio 2010 com suporte a C# (Standard, inclusive) ou ainda pelo Visual C# 2010 Express Edition.

XNA 4.0 permite que desenvolvamos games para Windows Vista, Windows 7, Xbox 360 e Windows Phone 7.

Para executar jogos feitos em XNA no Windows, a máquina deverá estar equipada com uma placa de vídeo que suporte WDDM 1.1 e DirectX 10 (ou posterior).

XNA Game Studio 4.0 está disponível (sem custos) aqui. A instalação é fácil.

Criando o primeiro aplicativo XNA

Tendo instalado o XNA Game Studio 4.0, podemos iniciar o desenvolvimento da primeira aplicação XNA. Isso é muito simples:

  1. No Visual Studio, selecione File –> New –> Project;
  2. Selecione “Installed Templates”;
  3. Selecione Visual C# –> XNA Game Studio 4.0;
  4. Na lista de templates (painel da direita), selecione  “Windows Game (4.0)”;
  5. Informe um nome para o projeto (sugiro FirstXnaApp);
  6. Informe a pasta onde deseja salvar seu projeto (a janela deverá estar semelhante a figura abaixo);
  7. Clique em OK.

image

Executando o aplicativo, teremos o seguinte resultado:

image

Nada muito animador, mas, acredite, é o “esqueto” para um game.

Perceba que se seu computador não possuir recursos de vídeo suficientes, a aplicação indicará falha durante a carga.

Vendo como as coisas funcionam

O template padrão do Visual Studio carrega uma boa quantidade de código. Comecemos pelo arquivo Program.cs:


              
using System;

namespace FirstXnaApp
{
#if WINDOWS || XBOX
    static class Program
    {
        static void Main(string[] args)
        {
            using (Game1 game = new Game1())
            {
                game.Run();
            }
        }
    }
#endif
}

            

O código de inicialização é muito simples. Na prática, uma instância da classe Game1 é instanciada e tem seu método Run executado.

Eis a classe Game1 (arquivo Game1.cs). Removi os comentários para reduzir tamanho do post.


              
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace FirstXnaApp
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
}

            

Neste código, podemos perceber a presença de dois atributos, um construtor e mais cinco métodos.

O objeto GraphicsDeviceManager

Como pode ser percebido em nossa listagem anterior, um objeto do tipo GameDeviceManager é instanciado logo na inicialização do Game. Este objeto é muito importante pois ele provê ao desenvolvedor uma forma padrão de acessar os recursos gráficos de um PC, de um Xbox 360 ou do Windows Phone 7.

GraphicsDeviceManager possui uma propriedade chamada GraphicsDevice que representa o dispositivo gráfico disponível no computador.

Toda comunicação entre um programa XNA e a placa de vídeo (ou melhor, a Unidade de Processamento Gráfico [GPU]) presente no dispositivo , ocorre através desse objeto.

O objeto SpriteBatch

O segundo atributo mantido pela classe Game1 é do tipo SpriteBatch. Trata-se do objeto que usaremos para desenhar Sprites.

Um sprite é uma imagem 2D ou 3D que usamos para compor uma cena. As cenas de jogos 2D são compostas por múltiplos sprites.

 

A imagem acima explica bem o conceito: cada jogador é um sprite, a imagem do background é um sprite, os placares são sprites, a mensagem “Fight!” é um sprite.

Numa generalização grosseira, podemos assumir que um sprite em nossa cena como uma espécie de controle em um formulário.

Carregando objetos do Game: Método Initialize e LoadContent

O método Initialize é o “lugar certo” para iniciar variáveis e outros objetos. A lógica de execução do tipo Microsoft.Xna.Framework.Game (de onde derivamos Game1) garante que o objeto GameDeviceManager já terá sido inicializado e poderá ser utilizado na inicialização de objetos que dependam de suas configurações.

O método LoadContent é chamado após a execução do método Initialize ou em qualquer momento onde seja aconselhável recarregar objetos gráficos do game (quando ocorrer mudanças nas configurações do dispositivo gráfico, por exemplo). Nesse método deverá ocorrer a carga de imagens, modelos 3D, sons, entre outros recursos.

The Game Loop

Logo que tenha sido concluída a carga do jogo, nos métodos Initialize e LoadContent, é iniciada uma rotina identificada como Game Loop.

O conceito de Game Loop representa uma mudança substancial na forma como estamos habituados a pensar quando codificamos aplicações “normais”. Na prática, nosso código XNA nunca fica “esperando” (por baixo do capô, para toda aplicação desktop existe um App Loop também, mas esse é um tema para outro post)

Essencialmente, um Game Loop consiste de uma série de métodos que são chamados repetidamente até que o jogo encerre. No XNA, o game loop chama repetidamente apenas dois métodos: Update e Draw. Toda a lógica do game deverá ser acionada através desses métodos. Perceba:

  • O método Draw é usado para (surpresa!) “desenhar coisas” . Obviamente, você pode colocar código que faz outras coisas sendo executado a partir desse método, mas… não é legal.
  • O método Update é lugar para “processar o estado do game”. Na prática, podemos dizer que todo processamento que não envolva desenho deve ser chamado a partir desse método. Onde acionar cálculo de atualização da posição de objetos? No método Update! Onde checar colisões? No método Update! Onde atualizar o placar? No método Update (desenhar o placar, é no Draw)! Verificar se o jogo foi concluído? No método Update.

Certo!?

Sem registro de eventos! Não insista!

Outro aspecto provavelmente novo para quem começa a desenvolver games é a inexistência de registro de eventos.

Geralmente, aplicações desktop são programadas apenas para responder eventos do usuário. Por exemplo, se estiver escrevendo um formulário de cadastro,  provavelmente fará o projeto da interface adicionando campos que precisam ser preenchidos, um botão para Salvar, outro para Descartar. Seu programa fará alguma coisa apenas quando um desses botões for pressionado, ou enquanto usuário faz alguma digitação, ou na mudança de foco. De qualquer forma, seu programa apenas reage a eventos disparados por ações dos usuários.

Por outro lado, desenvolvimento de games é apenas “influenciado” pelos eventos do usuário. O programa “continua rodando” no lugar de ficar esperando que alguma coisa aconteça. Por exemplo, no lugar de esperar que o sistema “anuncie” que o usuário moveu o mouse, o jogo precisa “perguntar” para o sistema se o mouse foi movido. Importante: um jogo sempre está executando alguma ação, independente de qualquer entrada do usuário.

Uma lógica mal implementada faz com que um jogo fique pouco responsivo. A “culpa” não é do dispotivo e sim do código mal-feito.

Examinando nosso código, percebemos esse conceito claramente no método Update.


              
// ...
protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    base.Update(gameTime);
}
// ...

            

Esse código faz com que o jogo seja encerrado quando o usuário pressionar o botão “back” no gamepad (controle).

The Game State

Como mencionado antes, o método Update é onde devemos realizar qualquer alteração de estado do jogo. Aliás, Game State é um conceito extremamente importante; é a forma como o jogo “sabe” o que está acontecendo.

Jogos tipicamente possuem “estados” significativamente diferentes, como:

  • mostrando um splash screen;
  • mostrando telas de placar;
  • mostrando seleção de jogadores;
  • durante a “ação”
    • jogador em estado normal;
    • jogador com mais força;
    • jogador “finalizando” uma jogada (fatality do Mortal Kombat, lembra?)
  • mostrando indicadores de fim-de-partida.

Tipicamente, todas as modificações no “estado” do jogo ocorrem dentro do método Update e são “desenhados” no método Draw.

Finalizando o jogo: método UnloadContent

Com XNA, sempre que o game loop é interrompido, o método UnloadContent é chamado.

Esse método é usado para “destruir” quaisquer conteúdos que tenham sido carregados, durante o método LoadContent, e precisem de algum tratamento especial. Tipicamente, XNA (assim como .NET) irá fazer a garbage collection. Entretanto, caso algum objeto que precise de tratamento especial tenha sido carregado, o método UnloadContent é o lugar onde essa descarga deverá ocorrer.

Fluxo básico de um jogo em XNA

Para encerrar por hoje, apresento um resumo gráfico do fluxo de execução de um jogo em XNA:

 

image

 

Mais uma vez:

  • A execução do game começa no método Initialize;
  • O método LoadContent é executado em seguida e faz a carga do “conteúdo” que será utilizado durante o jogo (texturas, modelos 3D, sons);
  • Inicia-se o GameLoop
    • Update “pega” os eventos e atualiza o estado;
    • Draw atualiza o display;
  • UnloadContent() é executado quando o jogo termina

Por hoje, era isso!

Smiley piscando

Posted in: C#, XNA