GameDev

GameDev #7: Conceitos de programação de jogos no XNA

Nos dois últimos artigos da coluna GameDev você teve a oportunidade de conhecer melhor o XNA, como ele se organiza, as suas camadas, vanta... (por Jones Oliveira em 08/03/2013, via Xbox Blast)

Nos dois últimos artigos da coluna GameDev você teve a oportunidade de conhecer melhor o XNA, como ele se organiza, as suas camadas, vantagens e possibilidades. Se você acompanhou bem e teve a curiosidade de pesquisar um pouco mais, supomos que já possui uma boa bagagem teórica para que possa avançar nos próximos artigos. Daqui em diante, prepare-se para pôr a mão na massa e colocar seu conhecimento teórico em prática. Arregace as mangas, prepare seu computador e vamos começar a programar para o XNA!

Prepare seu ambiente de desenvolvimento

No artigo de hoje criaremos um projeto XNA em branco, analisaremos sua estrutura e entenderemos os conceitos básicos por detrás do jogo.

Para que você possa acompanhar o artigo perfeitamente, é preciso preparar o seu computador para o trabalho pesado e instalar os softwares que daqui para frente serão suas ferramentas de trabalho e estudo. Para tanto, faça o download e instale o Microsoft Visual C# 2010 Express e o XNA Game Studio, ambos gratuitos. Após feito o download dos dois, instale primeiramente o Visual C# 2010 Express e logo em seguida o XNA Game Studio.

Se você está utilizando o Windows 8, então talvez tenha algum problema ao instalar o XNA Game Studio. Esse erro ocorre porque o Game Studio instala consigo uma versão do Games for Windows – LIVE nos bastidores. O problema é que há incompatibilidade entre o Games for Windows – LIVE que o XNA está tentando instalar e o Windows 8, e isso pode ser corrigido baixando e instalando manualmente a versão mais recente no seguinte link: http://bit.ly/W96StP.

Ao finalizar a atualização, basta tentar instalar o XNA Game Studio novamente e deverá funcionar.

O primeiro contato com o Visual Studio

Após as devidas instalações, abra o seu Visual C# 2010 Express e uma janela parecida com a imagem abaixo deverá aparecer. Clique em File->New->Project (ou aperte Ctrl+Shift+N) para criar um novo projeto. Na janela que aparecer, selecione a opção XNA Game Studio 4.0 e em templates selecione Windows Game (4.0) (segunda imagem abaixo).

Tela de inicialização do Visual Studio
Essa é a primeira tela do Visual Studio que você verá. À esquerda, perceba as opções de criar um novo projeto ou abrir um já existente.
No campo name você definirá qual o nome desse seu projeto e em Location o nome da pasta aonde seu projeto ficará salvo - escolha qualquer nome e a pasta que você quiser para isso e dê OK.

Tela de criação de projetos
Na tela de criação de projeto, você poderá selecionar uma das inúmeras opções de projeto. A ideia de selecionar um tipo de projeto é que o Visual Studio já cria uma estrutura básica que poderá te instruir a dar os primeiros passos no desenvolvimento dele.
Após o projeto ter sido criado, aperte F5 no seu teclado para compilá-lo e colocar o jogo para funcionar. É claro que não aparecerá jogo nenhum – ao invés disso, aparecerá uma entediante tela azul, que indica que você está pronto para desenvolver o seu primeiro jogo com XNA. Feche a janela azul e volte ao Visual C# 2010 Express e perceba que, à direita, a aba do Solution Explorer foi “povoada” por vários arquivos e pastas (imagem abaixo).

A Solution Explorer do Visual Studio
A Solution Explorer é a aba mais importante de todo o Visual Studio. Lá você gerencia todos os arquivos que fazem parte do seu trabalho.
Perceba que com os arquivos de imagem Game.ico e GameThumbnail.png seu projeto foi criado com dois arquivos de extensão .cs (Program.cs e Game1.cs), que são arquivos de código C#. Também foi criada uma pasta chamada Content que armazenará todo o conteúdo do jogo, tais quais sons, imagens, modelos 3D, texturas e por aí vai.

Antes de entender a estrutura dos arquivos de código do XNA, recapitulemos um pouco a estrutura básica de um jogo.

Estrutura genérica de um jogo

O último artigo apresentou um fluxograma básico sobre como um jogo funciona, e foi dito que uma vez dentro dele, este fica “dando voltas” dentro de si próprio verificando se todos os objetivos foram cumpridos para que se possa atingir o final do jogo. Dito isso, agora você olhará para essa ideia com olhares técnicos.


A lógica central de qualquer jogo inclui a preparação do ambiente no qual ele será executado, a execução do jogo em um loop até que o critério de fim de jogo seja alcançado e, finalmente, a limpeza desse ambiente. Se essa estrutura for representada tecnicamente, pode-se construir o seguinte pseudocódigo:
  • Inicialização dos gráficos, dispositivos de entrada de dados e som;
  • Carregar os recursos;
  • Iniciar o loop do jogo. A cada loop:
    1. Capturar os comandos do jogador;
    2. Executar os cálculos necessários (IA, movimentos, detecção de colisões etc.);
    3. Verificar se o critério de fim de jogo foi alcançado – em caso positivo, o loop para;
    4. Desenhar gráficos em tela, gerar sons e respostas aos comandos do jogador;
    5. Finalizar os gráficos, dispositivos de entrada e som;
  • Liberar os recursos (placa de vídeo, memória e CPU).
Claro, essa é uma visão bastante simplista, mas que já dá uma boa visão de como se deve “construir” o código do nosso jogo. Antes do XNA, os desenvolvedores não se preocupavam somente com essa estrutura, mas também em como tudo isso iria funcionar – todos os mínimos detalhes que não faziam parte do projeto de desenvolver o jogo em si deveriam ser pensados também.

Voltando ao Visual C# 2010 Express, observe atentamente os arquivos Game1.cs e Program.cs. Perceba que eles apresentam métodos que lhe permitirá afirmar que a estrutura dos jogos em XNA é bastante similar à estrutura do pseudocódigo que mostramos anteriormente. Sem entrar em maiores detalhes por enquanto, você tem o seguinte pseudocódigo nos dois arquivos:
Em programação, um método é uma sub-rotina que é executada para realizar alguma tarefa específica, como por exemplo realizar algum cálculo complexo. Leia mais a respeito.
  • Game1() – Inicialização geral (Game1.cs);
  • Initialize() – Inicialização do jogo (Game1.cs);
  • LoadContent() – Inicializa e carrega recursos gráficos (Game1.cs);
  • Run() – Inicia o loop do jogo (Program.cs). A cada loop:
  • Update() – Captura os comandos do jogador, realiza cálculos e testa o critério de fim de jogo (Game1.cs);
  • Draw () – Desenha os gráficos em tela (Game1.cs);
  • UnloadContent() – Libera os recursos gráficos.
Dica: Nos arquivos .cs do seu projeto, o XNA gera a estrutura básica do jogo e comenta cada um desses métodos. Os comentários, que estão entre as tags <summary>, especificam melhor o que cada um faz.
Agora vamos analisar mais detalhadamente os métodos do arquivo Game1.cs.

A inicialização do jogo 

Observe o seguinte pedaço de código:


Nele, pode-se perceber que a classe Game1 está sendo definida e criada, tendo como classe-pai a Microsoft.Xna.Framework.Game, que lhe permitirá trabalhar com gráficos, sons, controles e cálculos de lógica para o nosso jogo.
Em programação, classe é uma estrutura que descreve um conjunto de características e comportamentos similares de um objeto. É difícil explicar algo tão cheio de conceitos num espaço tão pequeno como este, e por isso recomendamos a leitura de um artigo de introdução à programação orientada a objetos e uma excelente apresentação da UFSE sobre o assunto.

Para estudo mais aprofundado, a internet está cheia de apostilas sobre orientação a objetos que você poderá baixar gratuitamente e estudar com mais afinco.
Logo em seguida são definidos e inicializados dois objetos do tipo GraphicsDeviceManager e SpriteBatch – esse permitirá desenhar textos e imagens 2D na tela do jogo, enquanto aquele dará acesso aos recursos do gerenciador de dispositivos gráficos para que você possa trabalhar com os gráficos do jogo.

Além disso, ainda há a definição da pasta raiz onde ficará todo o conteúdo do jogo – isso é importante, pois é a partir daqui que você terá acesso ao gerenciador de conteúdo do jogo.
Observação: daqui para frente entraremos em um linguajar mais técnico da coisa. Sempre que possível, tentarei esmiuçar o palavreado e explicar da forma mais simples possível. Entretanto, é importante que haja um conhecimento básico sobre programação e o paradigma de Orientação a Objetos. Caso você tenha interesse de verdade pelo assunto, aconselho o download e leitura de algumas apostilas de algoritmos, técnicas de programação e orientação a objetos.

O Graphics Device Manager (gerenciador de dispositivo gráfico)


Como seu nome sugere, o gerenciador de dispositivo gráfico lhe permitirá lidar diretamente com a camada gráfica do dispositivo. Ele inclui métodos, propriedades e eventos que permitem consultar e alterar essa camada. Trocando em miúdos, é o gerenciador que lhe dará acesso aos recursos da placa de vídeo.

Mais uma vez, antes do XNA havia a preocupação de programar a placa de vídeo para que ela trabalhasse corretamente com o jogo. Com a classe GraphicsDeviceManager do XNA, toda a preocupação, complexidade e detalhes de se trabalhar com uma placa de vídeo se esvai. Por ora, o que é preciso ter em mente é que você sempre utilizará o objeto criado a partir da classe GraphicsDeviceManager para realizar qualquer operação gráfica.

O Content Pipeline (gerenciador de conteúdo)

O gerenciador de conteúdo é um dos recursos mais interessantes do XNA. A forma com que foi desenvolvido permite que importemos qualquer tipo de conteúdo de ferramentas diferentes para o jogo. Em jogos que não utilizam XNA os desenvolvedores precisam se preocupar em como carregar tais conteúdos, onde esse conteúdo está armazenado, se existem as bibliotecas corretas para importação e execução e mais uma série de dores de cabeça desnecessárias.

O gerenciador de conteúdo do XNA dá conta de todo o recado, fazendo com que esse doloroso processo se torne bastante agradável. Basicamente o que ele faz é importar os conteúdos, processá-los com o compilador de conteúdo e gerar um arquivo de conteúdo (extensão .XNB) que será utilizado pelo jogo.

Fluxograma do Content Pipeline
O fluxograma do Content Pipeline dá a você uma ideia de como o gerenciamento de conteúdo acontece no XNA. Informações adicionais serão discutidas em artigos posteriores.
O mais interessante é que, apesar de abranger uma enorme quantidade de formatos de arquivos, você ainda poderá desenvolver seus próprios compiladores para trabalhar com um conteúdo específico criado por alguma ferramenta que o XNA ainda não dê suporte (inclusive já existe uma boa biblioteca disso na Xbox Live Indie Games). Discutiremos mais sobre isso em outros encontros.

Os métodos de inicialização do jogo

Vamos voltar um pouco ao início desse artigo. Analise o pseudocódigo que contém os métodos gerados e utilizados pelo XNA e perceba que há duas inicializações do jogo e que cujos propósitos ainda não foram devidamente explicados. Antes de iniciar o desenvolvimento do jogo em si, é preciso entender o porquê dessas duas rotinas de inicialização.

O método Initialize() é chamado apenas uma vez quando o método Run() (que inicia o loop do jogo) é executado. É no método Initialize() que são trabalhadas as rotinas de inicialização de componentes não-gráficos do jogo, como por exemplo preparar o conteúdo de áudio.

Os gráficos precisam ser carregados em um método diferente porque às vezes o jogo precisa recarregá-los. Para que o jogo rode em ambiente ótimo, os gráficos são carregados de acordo com as configurações da sua placa de vídeo. Quando as configurações da placa são alteradas, a resolução mudada, ou algo desse tipo, os gráficos precisam ser recarregados. Como o método Initialize() é chamado apenas uma vez, antes do loop do jogo ser iniciado, os gráficos são inicializados no método LoadContent(), que é chamada toda vez que o jogo precisar carregar ou recarregar os gráficos.

O loop do jogo

A maior parte do processamento do jogo acontece dentro do loop. É no loop que o jogo verifica os comandos do jogador, processa-os e dá o feedback no personagem, calcula a inteligência artificial, os movimentos são calculados e executados, as colisões entre objetos detectadas, a vibração do controle é ativada, o som é tocado, os gráficos desenhados na tela e os critérios para alcançar o fim do jogo checados. Ufa! Praticamente tudo ocorre no loop do jogo.

No XNA, o loop depende de dois métodos derivados da classe Microsoft.Xna.Framework.Game: Update() e Draw(). No Update() são incluídos todos os cálculos do jogo, enquanto no Draw() você desenha os componentes do jogo em tela. Para compreender melhor, observe os dois métodos:


Em relação às demais classes que já foram vistas, o que primeiro se pode denotar é que tanto o método Update(), quanto o Draw() recebem o parâmetro gameTime. No momento, o que é preciso saber é que esse parâmetro é crucial para a lógica do jogo. A partir dele é que o jogo saberá quanto tempo passou desde que o último loop foi executado. Isso é extremamente importante para que os cálculos corretos possam ser feitos – por exemplo, calcular a posição correta dos componentes do jogo de acordo com suas velocidades nele. Sem esse parâmetro, seria impossível organizar e desenvolver.

Outra coisa a ser observada é que, no método Update(), há um código pré-definido para sair do jogo quando é apertado o botão “Back” no controle do Xbox 360:


Layout do controle do Xbox 360
A classe GamePad permite acessar o controle e permite ativar a vibração dele. A captura dos comandos dados pelo usuário é em tempo real e não há nenhum atraso quanto a isso. Se você observar atentamente, verá que o trabalho com a interface do controle é bem simples e intuitiva, pois as propriedades são autoexplicativas:
  • GamePad: classe de acesso ao controle;
  • GetState: pegue o estado do controle;
  • PlayerIndex.One: apenas do primeiro controle;
  • Buttons: acessa os botões do controle;
  • Back: especificamente o “Back”;
  • ButtonState.Pressed: quando o botão for pressionado;
  • this.Exit(): sair do jogo.
Logo, sem nenhuma dificuldade, pode-se dizer que esse código está dizendo ao jogo que “quando o botão Back do controle 1 for pressionado, saia do jogo”. Simples, não?

Já o método Draw() inclui uma linha para limpar o dispositivo gráfico e preencher a tela do jogo com uma única cor – CornflowerBlue:


Como foi dito anteriormente, o gerenciador de dispositivo gráfico (representado aqui pela variável graphichs) é o principal meio de acesso às propriedades do dispositivo gráfico e suas propriedades. Nesse exemplo, em específico, a propriedade GraphicsDevice é utilizada e expõem as propriedades e métodos que permitem ler e configurar os vários detalhes da exibição do jogo.

Empolgante tela azul de um jogo vazio
Por incrível que pareça, essa é a tela inicial do seu primeiro jogo. Não se decepcione, isso irá melhorar já no próximo artigo.

A finalização do jogo

Fazendo uma analogia bem rasteira de como as coisas são, é possível comparar desenvolver um jogo em XNA como a organização de uma festa. Para realizar uma festa de sucesso, primeiro é preciso planejá-la e preparar o ambiente em que será realizada. Ao término da festa, o ambiente precisa ser limpo para ser “devolvido” nas condições em que foi pego. E assim acontece com o XNA também.

Você viu que, antes de começar o jogo em si, precisamos preparar o ambiente em que ele será executado [classes Game1(), Initialize() e LoadContent()], para então começar a “festa” (métodos Run(), Update() e Draw()). No seu fim, é preciso dar uma geral no ambiente. Esvazia-se a memória da plataforma em que o jogo está sendo executado e tudo o mais. O grande diferencial aqui está no método UnloadContent() do XNA.

O coletor de lixo, basicamente, atua automaticamente limpando a memória e liberando mais espaço para o jogo
É como se tivesse uma pessoa única e exclusivamente para executar a faxina no sistema ao término do jogo. O UnloadContent() possui o “coletor de lixo” (garbage collector) oriundo do .NET Framework que simplifica bastante o desenvolvimento e execução de rotinas de encerramento em jogos XNA. E, o melhor de tudo, ele não atua somente quando o jogo é finalizado, mas também enquanto está em execução. Dessa forma, não é necessário se preocupar muito com essa coleta de lixo dentro do jogo, a não ser que você esteja realmente desenvolvendo jogos complexos. Mas isso é assunto para depois.
Em programação, um coletor de lixo é um processo usado para a automação do gerenciamento de memória. Se você dispõe de um processo coletor de lixo, ele se responsabilizará por recuperar espaços inutilizados de memória e evitará problemas no vazamento dela automaticamente. Isso contrasta bastante com o que acontecia nos primórdios, em que o gerenciamento de memória era feito explicitamente pelo programador que dizia que regiões deveriam ser limpas e onde os dados do software deverão ser alocados.
Hoje você teve o primeiro contato com o Visual C# 2010 Express e com a estrutura de um jogo em XNA. Já deu para sentir de perto como as coisas funcionam e ter uma noção das bases para começar a desenvolver um jogo em XNA. No próximo artigo você verá como funciona o esquema de gráficos 2D no XNA. Desenharemos os primeiros elementos em tela, daremos vida a eles e os faremos colidir um com os outros. Caso tenham alguma dúvida, tire-a e participe na caixa de comentários. Fiquem atentos que o nosso próximo encontro promete. Até lá!

Confira a 6ª edição da coluna GameDev
Confira a 8ª edição da coluna GameDev

Revisão por Vitor Tibério

Escreve para o Xbox Blast sob a licença Creative Commons BY-SA 3.0. Você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original do mesmo.

Comentários

Google
Disqus
Facebook