Cursos / Jogos Digitais / Introdução a Jogos Digitais / Aula
Na aula passada, vimos que a programação de jogos exige uma série de cuidados e que o motor facilita muito a nossa vida. Também vimos várias formas de como ele faz isso, abstraindo as funcionalidades mais difíceis de programar e deixando o desenvolvedor focado na lógica do jogo. Assim, os motores são ótimas invenções e resolvem todos os nossos problemas! Ou pelo menos aqueles que nos dariam dores de cabeça na hora de programar os nossos jogos!
No entanto, as coisas não são tão simples assim! Acho que será melhor se entendermos um pouquinho mais como tudo começou.
Lembra que na aula passada nós falamos que algumas empresas de jogos começaram a desenvolver ferramentas que implementavam partes do jogo que se repetiam em diferentes projetos? Antigamente, o código era misturado entre a parte da lógica do jogo e o código necessário para a aplicação rodar e usar de forma adequada e eficiente o hardware disponível. Isso dificultava muito para que esse código pudesse ser reutilizado, pois você precisava identificar dentro de um código-fonte gigante quais partes tratavam de cada coisa. Uma bagunça!
E então veio DOOM. Um jogo que não só alavancou o gênero do FPS (e ajudou a definir vários padrões para esse gênero), mas também contribuiu significativamente para o desenvolvimento de jogos.
A equipe que desenvolveu DOOM se preocupou em projetar o jogo com uma separação dos componentes de programação e dos recursos de arte. Dessa forma, uma equipe de artistas poderia criar novos recursos e fazer um “novo” jogo (com a parte de funcionamento igual ao DOOM) no que concerne ao visual. Esse primeiro esforço de separação entre a parte da programação e a parte artística fez com que o termo “motor” fosse utilizado pela primeira vez para se referir a essa aplicação base que poderia ser alterada para criar novos jogos.
Perceba que essa arquitetura não era tão flexível quanto a que temos hoje em dia, mas apresenta uma noção muito importante do funcionamento dos motores: a modularização. Os componentes são separados de acordo com a sua funcionalidade, assim, partes que tratam de uma mesma atividade (desenhar coisas na tela, por exemplo) estão juntas na ferramenta. Isso facilita dois aspectos do processo de desenvolvimento:
Perceba que a separação proposta por DOOM ainda é bastante limitada: usando o motor do jogo, você só conseguia criar jogos parecidos com DOOM, mudando a arte. Com o tempo, novas propostas de modularização foram surgindo, gerando vários níveis de flexibilidade para o motor. Primeiramente, isso se restringiu a motores para jogos do mesmo gênero, ou seja, motores que possuíam otimizações para problemas comuns em jogos FPS ou de estratégia, por exemplo. Em Counter Strike, um jogo de FPS, o modo como a câmera e o nível de detalhe em que o personagem do jogador e os adversários são exibidos na tela é totalmente diferente de um jogo de estratégia em tempo real como Starcraft. Esses motores buscavam facilitar o desenvolvimento trazendo as melhores implementações de funcionalidades comuns (iluminação, renderização do espaço, representação dos elementos do jogo em memória, câmera do jogo, geração de rotas dos personagens no jogo, etc.). Com o tempo, motores mais generalistas passaram a ser criados, de forma que o mesmo motor pudesse ser usado para construir jogos em qualquer gênero, como é feito no Unreal 4 e no Unity. A diferença entre essas abordagens ocorre no grau de reuso que elas permitem. Vamos pensar em um exemplo para clarear essa questão!
Imagine que você deseja fazer um jogo de corrida. Uma das funcionalidades que, com certeza, precisará é desenhar o carro na tela, não é mesmo? Um motor poderia ter diferentes abordagens para isso:
Existe uma relação de custo/benefício entre flexibilidade de uso e facilidade de desenvolvimento. É óbvio que, se o seu foco é apenas um jogo de Fórmula 1, o primeiro motor já seria o suficiente. Você não precisaria se preocupar em desenhar o modelo do carro, pois ele já estaria pronto! No entanto, você ficaria limitado a trabalhar apenas com os elementos que ele lhe fornecesse, então, se quisesse colocar aquela paisagem linda com montanhas rochosas e o motor não deixasse, poderia esquecer. No outro extremo, temos o motor que deixará você fazer o que quiser, porém precisará efetivamente fazer várias coisas, pois ele lhe dá uma funcionalidade básica de desenho em tela a partir de um modelo 3D, mas você terá de produzir todos os modelos para ele.
E já que falamos em reuso, deixe-me aproveitar o exemplo para falar de outro ponto! Imagine um motor com a função desenhaCarro que só consegue desenhar carros de Fórmula 1. De repente, ela resolve modificar essa função para que os desenvolvedores de jogos possam usar a ferramenta para desenhar não apenas um tipo de carro, mas também outros veículos. Uma grande vantagem de uma ferramenta modularizada diz respeito à definição das interfaces de como usar as funcionalidades, permitindo a transparência sobre como elas são implementadas. Agora imagine que, para usar a função desenhaCarro, o desenvolvedor do jogo só precise escrever a seguinte linha:
desenhaCarro(numero1)
Onde numero1 é o carro que ele quer desenhar. Ao alterar a função, a empresa que desenvolve o motor pode mudar a interface da função e dizer que a partir de agora o jogo precisa informar o carro e o tipo do modelo a desenhar, por exemplo:
desenhaCarro(numero1, “Ferrari”)
OK, isso já é legal! Mas ainda não é perfeito, porque o desenvolvedor do jogo precisará fazer ajustes no seu código para se adequar à nova interface ou, então, usar uma versão antiga da ferramenta. Agora imagine uma função mais genérica, como iluminaSala, que é responsável por pegar uma fonte de iluminação no mundo do jogo e simular todos os raios de luz da cena. Digamos que ela é usada assim:
iluminaSala()
Depois de muita pesquisa, a equipe desenvolvedora do motor descobre um jeito muito mais eficiente de fazer essa simulação, que dobrará a velocidade da renderização da luz e deixará os jogos mais rápidos consumindo menos memória. Mas, para isso, eles precisam implementar a função iluminaSala completamente, mudando inclusive as estruturas que são utilizadas internamente. A função que tinha 30 linhas passa a ter 500, porém a equipe não muda a interface de chamada.
Sabe o que o desenvolvedor de jogos usuário do motor precisa fazer no seu código para usar essa nova função? Nada. Continua chamando-a iluminaSala(). E essa é uma grande vantagem que se obtém com a transparência de funcionalidades do motor: você não precisa saber como a coisa funciona internamente, precisa apenas saber como utilizá-la.
Essa discussão toda foi para falar que nenhum motor é perfeito, mas o uso de um deles torna a sua tarefa muito mais fácil tanto do ponto de vista técnico como no ponto de vista de projeto. Porém, mesmo com uma gama diferente de motores, existem alguns componentes que são comuns entre eles, por serem necessários em praticamente todos os jogos. Falaremos um pouco sobre cada um deles!
Versão 5.3 - Todos os Direitos reservados