Cursos / Jogos Digitais / Introdução a Jogos Digitais / Aula
Vamos começar a programar?
Calma, calma.
Vocês estão dando os seus primeiros passos na programação dentro do Curso Técnico (ou será que já chegaram sabendo?) e já estudaram várias estruturas que são usadas na construção de programas, como os laços de repetição, os condicionais, as variáveis, as funções, etc. Um jogo, como qualquer outro programa, também é construído usando essas estruturas, porém com uma complexidade mais alta. Isso acontece porque um jogo normalmente é composto por vários sistemas, pedaços maiores de código que compõem funcionalidades específicas operando de forma integrada dentro de uma arquitetura definida para o projeto.
A ideia é parecida com a arquitetura tradicional de casas que você conhece. Um projeto de arquitetura de um imóvel serve para dar uma ideia de como os elementos serão utilizados na sua construção, como os cômodos serão distribuídos na área do terreno, como eles serão interligados de forma a facilitar a circulação das pessoas e a ventilação da casa, qual a função de cada cômodo e o que é preciso na construção para que cada um possa desempenhá-la (por exemplo, não pode esquecer o encanamento no banheiro). A arquitetura de um software tem o mesmo propósito, pois a partir dela definiremos quais tipos de módulos ou componentes serão necessários para que o programa funcione, como eles irão se comunicar/trocar informações ao longo da execução do programa, quais tipos de padrões de código existem para realizar determinadas tarefas, e por aí vai.
Dito tudo isso, não existe uma arquitetura padrão para jogos, normalmente fica a critério da equipe de desenvolvimento escolher ou criar, mas com objetivos a serem considerados: facilitar a programação do jogo, aumentar o desempenho/eficiência da aplicação, tornar mais fácil o desenvolvimento para dispositivos móveis, prover uma interface mais intuitiva para a equipe de artes, permitir uma maior flexibilidade na execução de alterações e testes, etc. Mesmo assim, é comum observar a divisão do jogo nas seguintes partes:
- Está muito lento esse jogo!
- O controle não responde direito!
- Travou na hora que eu ia derrotar o chefão!
- Esse jogo não presta, é muito chato!
- Não acredito que aconteceu isso com meu personagem!
- Como eu ia adivinhar que tinha de pegar esse item na primeira fase para poder zerar o jogo?
- Que jogo feio!
- Cadê o inimigo que eu estava atacando? Desapareceu do nada, mas continua tirando minha energia!
- Será que essas partes faltando na tela são intencionais?
Para não dar um nó na cabeça de vocês, agora faremos uma abordagem em alto nível da programação de jogos. Vocês terão disciplinas mais à frente no curso (logo duas, Motores de Jogos I e II) que entrarão em mais detalhes nos aspectos técnicos. Dando início à nossa discussão, vamos primeiro pensar em um jogo e como ele funciona. Que tal usarmos o Pacman como exemplo? Sim, eu gosto de Pacman! Além disso, ele é um jogo simples de entender e possui vários elementos que enriquecem a nossa discussão. E dá para experimentar de graça, é só ir no Google e jogar online mesmo!
No jogo do Pacman, você controla uma bolinha amarela que tem como objetivo comer todos os pontinhos do mapa, sem deixar que fantasmas coloridos lhe capturem. Uma aventura, no mínimo, mitológica! Para mover o Pacman, o jogador deve usar as teclas de direcionais (ou outro esquema que ele escolher) e movimentá-lo para cima/baixo e de um lado para o outro. O mapa do jogo é um labirinto que consiste em vários caminhos delimitados por paredes, e o Pacman não pode atravessar essas paredes (curiosamente, nem os fantasmas, mas pode ser só preguiça deles…). Caso os fantasmas peguem o Pacman, ele perde uma “vida” ou tentativa para finalizar o jogo (o jogador usualmente começa com três tentativas). Existem pontinhos especiais maiores que, ao serem comidos, dão um poder extra ao Pacman e faz com que ele possa comer os fantasmas, derrotando-os temporariamente. Cada fantasma possui um padrão de movimento próprio e o jogador deve ter cuidado e atenção para não ser encurralado por eles.
Essa é uma descrição curta, porém que dá uma noção geral dos vários elementos do jogo. Vamos analisar alguns pontos: primeiro, existe uma diferença entre o Pacman estar “energizado” e poder derrotar fantasmas, e o modo normal. Essa diferença não afeta apenas o confronto com os monstros, mas até a forma como eles se movimentam (eles fogem do jogador), ou seja, existem estados diferentes dentro do jogo que alteram o modo como os elementos se comportam ou se apresentam para o jogador. Dizemos que esse conjunto de atributos é o estado do jogo, e para que tudo funcione corretamente, esse estado precisa ser mantido constantemente atualizado. Em nível de programação isso significa manter um conjunto de variáveis e estruturas com informações sobre os vários elementos do jogo, e executar ações na medida em que o estado se transforma. Por exemplo, se todas as bolinhas presentes no mapa acabarem, o jogo precisa saber dessa ocorrência e que o jogador venceu a partida, exibindo informações na tela e perguntando se ele deseja jogar novamente ou sair. Se o Pacman for pego por um fantasma e não tiver mais tentativas, a temida tela de Game Over será exibida para o jogador!
Outro ponto importante é que o jogo ocorre em um ciclo de ações: o jogador vê o mapa, executa um comando, esse comando é processado pelo jogo, o estado do jogo é atualizado e apresentado ao jogador através do redesenho da tela. Esse ciclo se repete até que o jogador encerre a partida (ou se você jogar LOL até o cliente dá um crash naquela partida que você ia carregar com nota S…). Na programação, isso corresponderia a um laço que possui uma ou mais condições de parada, e dentro desse laço existem funções ou condicionais que verificam o estado do jogo (comeu todas as bolas? Foi atingido por um fantasma? Estava energizado?), determinam as ações que devem ser executadas pelos objetos/inimigos (no caso do Pacman, o movimento tanto dele como dos fantasmas) e realizam o redesenho do mapa com o resultado das ações do jogador. Em jogos como xadrez, a partida não avança enquanto o jogador não entrar com algum comando. No Pacman é diferente: se o jogador ficar parado sem fazer nada, os fantasmas continuam andando pelo mapa, caçando vorazmente o jovem comilão amarelo. Nesse caso, o laço do jogo possui um tick, como se fosse um ponteiro do relógio, e a cada vez que avança, as ações do laço do jogo são executadas. Isso permite que os jogos fiquem dinâmicos e as inteligências artificiais bem-implementadas (ou roubando! Alguns desenvolvedores deixam a inteligência saber mais coisas do que deveria para deixar o jogo mais difícil para você!), tornando a nossa experiência mais emocionante!
Outra parte importante na programação de jogos são as noções de captura de comandos e detecção de eventos: caso o jogador aperte para cima, o Pacman deve começar a se mover naquela direção, a menos que tenha uma parede. E se ele passar por uma bolinha? Então ela será apagada do mapa. E se bater em um fantasma? Aí depende, se estava em condições normais, o Pacman perde uma tentativa e volta para o ponto de início do mapa, se estava energizado, ele derrota o fantasma e ganha pontos. Perceba que tanto o comando para cima como a colisão com o fantasma (quando o Pacman bate nele) e com a bolinha (quando o Pacman passa por cima dela) são eventos que mudam o estado do jogo. O programa deve ser capaz de, a cada ciclo de interação, detectar quais desses eventos ocorreram e, validando com o estado atual do jogo, realizar as alterações necessárias para apresentar o novo estado do jogo. Esse processo passa por validações, inclusive de qual evento foi acionado primeiro, já que podem existir eventos conflitantes. Imagine um jogo online de combate entre jogadores, como o League of Legends: a sequência em que os feitiços e habilidades dos jogadores são acionados pode ser essencial para o resultado da partida, e uma ordem de avaliação equivocada dos eventos gerará um resultado inconsistente (leia-se: catastrófico e com muito xingamento) para o jogo!
Um detalhe da captura de comandos: se você quer o seu jogo multiplataforma, então provavelmente precisará codificar funções diferentes levando em conta cada dispositivo possível de interface do jogador. Então normalmente se faz uma função para capturar o touch do smartphone, uma para mouse/teclado, uma para controles… É um retrabalho necessário para alcançar as diferenças entre os dispositivos e/ou plataformas que podem ser executados pelo seu jogo!
Quase tudo o que falamos até aqui corresponde ao que chamamos de lógica do jogo, ou seja, são partes da programação preocupadas em como o jogo funciona no sentido da interação com o jogador. Seria a parte da programação totalmente independente do hardware ou sistema no qual ele roda, seja no celular ou no computador, a lógica do jogo deve ser a mesma. Só que existe também toda uma parte de programação voltada às questões de como o jogo vai rodar no sentido operacional da coisa: como o sistema operacional e o hardware das plataformas serão utilizados para fazer com que o jogo rode suavemente, sem atrapalhar a experiência do jogador naqueles 60 fps que todo jogador almeja ter!
Aqui estamos falando de questões técnicas de computação ao que chamamos de parte operacional no começo do texto: como o jogo será gerenciado em termos de memória. No Pacman, todo o mapa é visível na tela, mas e se não fosse? Eu colocaria o mapa todo na memória, arriscando deixá-la cheia e com isso perder desempenho de execução? Ou eu carregaria só a parte visível e arriscaria a possibilidade de falhas no desenho da tela porque o hardware ainda não disponibilizava a próxima parte do mapa carregado na memória para mostrar? Essas questões de como será utilizada a memória, o processador, armazenamento físico em HD, comunicação em rede e outros detalhes técnicos da execução do programa compõem talvez uma das partes mais difíceis da programação de um jogo. E sabe por quê? Porque cada plataforma tem seus padrões e formas de acessar/controlar esses dados, e o programador teria de fazer uma codificação personalizada para cada plataforma. Assim, caso deseje fazer um jogo multiplataforma, prepare-se para codificar várias funções que fazem coisas similares!
Acho que esses poucos exemplos já nos passam uma ideia de como um jogo é feito e do trabalho necessário para gerenciar tudo isso, não é? E estamos falando de Pacman, um jogo que em termos de programação é simples de fazer. Imagina agora esses jogos mais novos, como No Man’s Sky, que possui um universo gerado de forma aleatória e com trilhões de lugares para serem explorados, ou o novo Doom com seus gráficos e animações ricos em detalhes… E nem falamos sobre o trabalho que dá desenhar na tela esses jogos 3D!
Calma! Nem tudo está perdido! Após alguns anos desenvolvendo jogos, as empresas perceberam que havia algo em comum em seus projetos: elas passavam muito tempo codificando coisas similares ao que já havia sido feito em projetos passados. Ora, existem coisas que todos os jogos devem ter, não é mesmo? Todo jogo precisa controlar um estado de informações; desenhar na tela para o jogador poder ver o que está acontecendo; capturar os comandos do jogador para saber qual decisão foi tomada; e por aí vai. Eles perceberam que se fizessem um programa abrangendo a maior parte desses detalhes técnicos que se repetem entre jogos, permitindo que focassem a produção nos detalhes da lógica do jogo e na implementação apenas de funcionalidades peculiares de cada jogo, eles conseguiriam fazê-lo mais rapidamente, reutilizando funcionalidades que já foram testadas várias vezes e, por isso, apresentavam um menor risco da ocorrência de erros. E assim surgiram os primeiros motores de jogos!
Versão 5.3 - Todos os Direitos reservados