MAC499

Projeto de Formatura Supervisionado

 

 

 

 

 

Tiago Tagliari Martinez

NºUSP 2253604

 

 

 

Monografia Final

 

Introdução *

Metas do Projeto *

O que é, afinal, um motor para jogos? *

Mip Is Portable *

  • MIP Gráficos *

    MIP Som *

    MIP Interação *

    Outros módulos *

  • O Jogo Patto *

    O Engine Gannso *

  • Gerenciamento Interno dos Objetos *

    Interface com o Usuário *

    Inteligência Artificial *

  • Considerações Finais *

  • Projetos Futuros *
  •  

    Introdução

    Nosso projeto de formatura foi a construção e implementação de uma "engine" (motor) para facilitar a construção de jogos e outros aplicativos multimídia para computadores pessoais utilizando os ambientes Microsoft Windows e Linux com X-Windows.

    O idéia de "motor", como utilizada acima, se refere à uma coleção de bibliotecas, "frameworks" e utilitários que façam grande parte do processamento bruto exigido numa aplicação multimídia típica, assim o desenvolvedor da aplicação pode se concentrar na lógica e "inteligência" do sistema, e não em detalhes de implementação, como construção de janelas e operação com imagens e sons. Como um bônus, esse isolamento dos detalhes de implementação permite que a aplicação seja facilmente portada para diversos ambientes operacionais.

    Originalmente esse projeto se baseia em projetos anteriores que cada um de nós tínhamos em caráter pessoal. Todos jogaram muitos jogos produzidos por terceiros, e alguns já haviam desenvolvido algumas coisas pequenas que poucas vezes foram para frente, essa mentalidade comum foi o que mais incentivou a escolha desse projeto como projeto de formatura.

    O grupo é formado pelos seguintes alunos:

    Tiago Tagliari Martinez

    Luiz Gustavo Martins

    Ricardo Bueno Cordeiro

    Ricardo Skubs

    Roberto Augusto Giacomo da Motta

    Marcus Harada Penna

    Fomos supervisionados e coordenados pelo professor Marcos Dimas Gubitoso.

     

    Metas do Projeto

    Nossa meta básica era o desenvolvimento de uma plataforma que facilitasse a construção futura de jogos além de um framework específico para a criação de jogos no estilo "covert-action" (conhecidos como jogos de espionagem, normalmente envolvem o personagem ter que fugir de determinado lugar ou pegar algum item evitando ao máximo chamar a atenção, e usando a violência o menos possível).

    Para tanto definimos as seguintes metas do projeto:

    Algumas dessas metas se mostraram mais complexas de outras, principalmente permitir que a biblioteca tivesse a mesma quantidade de recursos em todos os ambientes. Para isso poderíamos (1) proporcionar uma série de funções básicas, que seriam usadas de diversas maneiras para alcançar todos os efeitos desejados ou (2) colocar suporte na biblioteca para todas as funcionalidades que pudéssemos imaginar e implementá-las a mão em ambientes que não possuíssem suporte interno para elas.

    A solução (2) tende a melhorar a performance de certas coisas, pois é sempre mais rápido usar código específico de cada plataforma a usar um código portável, porém a biblioteca ficaria muito grande e complexa para manter e portar, o que é um problema grave dado o tamanho reduzido da nossa equipe.

    Portanto a solução escolhida foi a (1), definindo algumas funções básicas e fazendo todo o resto do código usá-las facilitaria a portabilidade (menos coisas teriam que ser rescritas) porém com um pequeno custo na performance, algo que consideramos aceitável.

     

    O que é, afinal, um motor para jogos?

    "Motor" é o nome genérico dado pelos profissionais do ramo ao conjunto de objetos, algoritmos e padrões que faz o jogo funcionar. Por exemplo, num jogo que realiza renderização (projeção) de objetos tridimensionais em tempo real, o engine seria composto do renderizador, da inteligência artificial dos inimigos, gerenciamento da interação dos objetos e armazenamento dos diversos tipos de dados necessários, isso tento no formato de arquivo utilizado como quais tipos de informações seriam armazenadas.

    Para demonstrar o que realmente isso significa vamos dar um exemplo prático. Uma empresa tradicional do ramo de desenvolvimento de jogos é a Id Software, uma empresa americana que atual a mais de dez anos no ramo e tem no currículo uma série de produtos inovadores (como Wolfstein 3D, Doom e Quake).

    Para desenvolver Quake, por exemplo, que é um "shooter" (o personagem tem que achar seu caminho por um labirinto 3D matando tudo que apareça pela frente com uma pequeno arsenal a disposição), a Id desenvolveu um renderizador 3D, junto com formatos para armazenar os modelos e as texturas necessárias em disco e uma linguagem de script que permitiria uma equipe de IA desenvolver a inteligência dos inimigos conforme necessário.

    Além disso uma série de ferramentas permitiam que os designers projetassem os cenários em programas famosos (como 3D Studio) e usassem no jogo, assim para se criar um novo cenário do jogo, basta criar um mapa 3D, colocar os inimigos, definir qual script de inteligência eles usariam e o engine cuidaria do resto.

    Quando uma outra empresa tem a idéia de criar um jogo cujo funcionamento é parecido eles, ao invés de criar um motor próprio, compram o direito de usar o engine da Id, ganhando com isso o direito de modificar o código conforme necessário, e junto com isso as ferramentas para criação de cenários (que inclusive foram liberadas para uso gratuito para todos pela Id, assim qualquer um pode criar um cenário em casa, apenas tendo a paciência e habilidade necessárias).

    Portanto o engine fica um nível acima da interação com o sistema operacional, funcionando como um coordenador do funcionamento do jogo, permitindo que uma série de parâmetros sejam especificados a fim de customizar o funcionamento para o que se é almejado.

     

    Mip Is Portable

    A camada de portabilidade, responsável por toda interação com o sistema operacional, foi chamada de MIP (um nome recursivo para Mip Is Portable), porém ainda tínhamos que definir o que, exatamente, ela faria e como seriam alguns detalhes de implementação.

    Para preencher os requisitos básicos esta biblioteca deveria ser eficiente (o mais próximo possível de se usar diretamente funções do ambiente operacional), fácil de usar, expansível e não impor limitações nas formas de uso.

    A MIP que temos agora é muito influenciada por um projeto que já tínhamos iniciado no ano passado, antes do projeto de 499, porém para uso no projeto algumas coisas foram melhoradas, repensadas e/ou rescritas.

    Como comentei anteriormente o primeiro passo seria definir quais funcionalidades seriam dependentes da plataforma, para que o resto pudesse ser feito em função delas. Muito dessa decisão foi tomada em função do que cada ambiente proporciona (principalmente o Windows, ambiente principal para desenvolvimento, e sua biblioteca DirectX para acesso direto ao hardware).

    As funções da biblioteca podem ser divididos em módulos, cada um deles com requerimentos próprios, portanto com funcionalidades distintas.

    MIP Gráficos

    Uma das minhas maiores atribuições no projeto foi o desenvolvimento deste módulo da MIP.

    Esse módulo trata de enviar dados para o vídeo. Isso representa, basicamente, o uso de bitmaps (ou mapa de bits) que são matrizes onde cada posição guarda um valor de cor utilizado para desenhar na tela.

    Assim sendo a funcionalidade básica se resume a criar e manter bitmaps. Algumas bitmaps tem funções especiais, uma delas representa o vídeo e qualquer coisa desenhada nela altera o que é apresentado para o usuário. Também temos uma tela-secundária, ou "back buffer", criada num espaço da memória de vídeo não utilizada, que possibilidade animações rápidas e suaves.

    O funcionamento desse back-buffer esta ligado com o funcionamento do hardware de vídeo. Uma placa de vídeo possui uma quantidade de memória, onde fica armazenado o bitmap que representa o que esta na tela num determinado momento. Normalmente, porém, uma grande quantidade de memória fica livre.

    Se usarmos essa memória livre para guardar o próximo quadro de animação do jogo que será mostrado podemos colocá-lo na tela sem efetuar nenhuma cópia de memória, apenas instruindo a placa de vídeo para usar outra região da memória como memória primária.

    Isso revolve um problema comum em diversos tipos de jogos. Normalmente um jogo fica redesenhado a tela dezenas de vezes por segundo, geralmente iniciando com uma tela preta e redesenhando todos os objetos visíveis por cima. Isso gera um problema chamado "flicker", os últimos objetos a serem desenhados ficam um grande período de tempo sem aparecer. Para o olho humano isso pode criar o efeito de translucência, se feito rápido o bastante, ou o objeto parece piscar, se o redesenho não for rápido o bastante, o que é normalmente o caso.

    No caso do Windows o DirectX nos da um suporte direto a esse tipo de operação, no caso do Linux isso somente seria permitido ao root do sistema, portanto usamos uma "simulação" desse efeito usando um back-buffer na memória do sistema e copiando por cima da memória de vídeo. Isso implica numa perda de performance, porém não encontramos nenhuma maneira melhor de simular esse efeito.

    Portanto a funcionalidade básica para o subsistema gráfico é:

    Além disso também implementamos:

    Por essa ser a operação mais freqüente em bitmaps, portanto deveria ser executada o mais rápido possível, o que depende do suporte da plataforma.

    MIP Som

    Som é uma componente muito importante para que o efeito do jogo seja cativante. Jogar um jogo sem som é próximo a ver televisão sem som -- você pode até saber o que está acontecendo, mas certamente não tem muita graça.

    A implantação do módulo de som foi uma das partes mais complexas do sistema. O principal motivo disso é que, ao contrário do suporte a gráficos, muitas plataformas possuem pouco, quase nenhum, suporte à simples reprodução de sons.

    Isso pode ser claramente percebido na diferença entre Windows e Linux. O Windows, por meio do já citado módulo DirectX, possui suporte completo para reprodução de ondas sonoras, incluindo suporte do próprio sistema a mixagem (mistura) de sons e controle de volume.

    Já no Linux o único suporte universal é o dispositivo /dev/dsp. Escrevendo dados em determinado formato neste dispositivo envia os dados diretamente para a placa de som. Porém, se você pretende tocar mais que um som simultaneamente, é sua responsabilidade fazer a mixagem dos sons.

    Muito interessante, ao meu ver, foi a solução dada para esse módulo do sistema. Definimos as funções básicas:

    Fazer isso funcionar no Windows foi relativamente fácil, porém pelos motivos vistos acima uma solução mais complexa seria necessária no Linux.

    Para tanto acabamos implementando uma versão reduzida da biblioteca de som do DirectX no Linux. Essa biblioteca cuida de criar uma thread separada, responsável pela mixagem e envio de dados para o dispositivo correto. Isso implica numa camada a mais de abstração, porém acabou resultando numa biblioteca funcionado de "baixo nível" para tocar sons no Linux, algo que já vimos, porém ainda é bastante incomum.

    MIP Interação

    Esse módulo é um nome genérico para a interface com o usuário através do mouse e do teclado. Por ser algo bastante desenvolvido (talvez por ser muito necessário...) em todas as plataformas, isso não representou grandes problemas.

    O único detalhe é que os valores dados as teclas são diferentes no Windows e no XWindows. Porém descobrimos que já existe um arquivo include para C que liga os valores de teclas do Windows (constantes na forma VK_<nome da tecla>) para seus respectivos valores no XWindows.

    Outros módulos

    Também planejamos a inclusão de um módulo para suporte a jogos pela rede e um módulo para renderização de cenas 3D. Não temos muitas definições sobre esses módulos, porém pretendemos usar UDP como protocolo para rede e OpenGL para renderização.

    Esse módulos acabaram não entrando na MIP até agora por questões de tempo e por julgar que os outros módulos eram mais importantes e mereciam maior prioridade.

     

    O Jogo Patto

    Quando o desenvolvimento da MIP já estava em estágio avançado, mas ainda não tínhamos definições sobre vários pontos da engine que iríamos criar, tivemos a idéia de ressuscitar outro projeto antigo, originalmente chamado Shoot!, e portá-lo para a MIP de maneira a testar seus recursos. Esse jogo foi chamado de Patto e esta em estágio avançado de desenvolvimento.

    A principal função do Patto é testar os recursos da MIP. Claro que nos esforçamos em faze-lo divertido de jogar, afinal para certos testes é necessário jogar por períodos prolongados, porém muito do que o jogo faz (ou não faz) deixa claro essa finalidade.

    Patto praticamente não possue nenhuma inteligência articial nos inimigos. Eles apenas aparecem na parte superior da tela e descem, ricocheteando nas bordas da tela, enquanto atiram no personagem principal, uma nave diferente que atira mísseis.

    Porém o jogo inclui: efeitos sonoros, incluindo trilha sonora e efeitos especiais para tiros e explosões, efeitos gráficos, incluindo desenho com alpha-blending1 e lighten2, tudo isso funcional tanto no Windows quanto no Linux. Também tivemos sucesso portanto uma versão inicial para rodar nas estações das E.T.s, e o aluno Márcio Calixto Cabral portou uma versão inicial do Patto para estações de trabalho Silicon Graphics que ele usa no LSI da Poli.

    O Engine Gannso

    Tendo um teste funcional da MIP, já podíamos iniciar o trabalho na próxima etapa do projeto: o engine de jogos Gannso.

    O engine foi dividido nos módulos:

    Gerenciamento Interno dos Objetos

    O funcionamento de qualquer jogo pode ser visualizado como a interação entre diversos objetos distintos (que podem representar inimigos, itens, portas, alavancas, etc.) Por isso o gerenciamento dos objetos do jogo é uma parte fundamental para que o aplicativo funcione da maneira devida.

    Para facilitar o trabalho os objetos foram agrupados em grupos funcionais (objetos que se movem ou estáticos, objetos com animações ou sem, etc.) tendo esses grupos em mente, foi montada uma hierarquia de classes para representá-los.

    Por via de herança e de métodos virtuais do C++, todos os objetos podem ser guardados numa grande lista, independente do que eles realmente representam, essa lista é iterada periodicamente e uma função de atualização (que possui um comportamento diferente dependendo do tipo real da classe) é chamada.

    Interface com o Usuário

    Esse tópico define todas as formas que o jogo interage com o usuário final. Isso inclui como ele reage as ações do jogador (via mouse ou teclado) e como ele apresenta o estado do jogo. Como essa parte normalmente fica mais para o final do projeto, e como o engine acabou não chegando no estágio final, pouco foi feito.

    Apenas definimos como é controlada a movimentação do personagem principal (através de uma combinação do mouse e do teclado) e poucos detalhes da interface gráfica, muitos ainda não implementados.

    Inteligência Artificial

    É a IA de um jogo um dos grandes componentes que separa os lançamentos cotidianos, que aparecem por qualidades técnicas dos gráficos ou do som, dos grandes sucessos que se mantém por longos períodos.

    Implementar a IA de um jogo não é uma tarefa simples, muitos detalhes devem ser levados em conta. Para que o jogo seja justo, é importante que os elementos controlados pelo computador não tenham nenhuma forma de vantagem sobre o usuário, isto é, eles devem se comportar como se fossem controlados por um outro usuário humano.

    Essa parte infelizmente não avançou muito no desenvolvimento do nosso engine. Foi implementado um algoritmo de busca de caminhos (path-finding) baseado no A* (A-star), um sistema parecido com o algoritmo de Dijkstra para menor caminho num grafo, mas que se preocupa em criar soluções aproximadas no menor tempo possível, não necessariamente a melhor solução.

     

    Considerações Finais

    Acho que o maior problema encontrado durante o desenvolvimento do projeto não foi de caráter técnico, mas sim organizacional. Nossa formação no IME nos deu uma boa base teórica, e um pouco de base prática, suficiente para o desenvolvimento dos algoritmos que se fizeram necessários.

    Porém não foi fácil coordenar o trabalho de seis pessoas que trabalham e se preocupam com se formar na faculdade, e consequentemente possuem pouco tempo livre. Foi notado que reunir as pessoas num mesmo local, discutindo e resolvendo os problemas na hora, é muito mais eficiente que qualquer forma de trabalho a distância.

    Durante o desenvolvimento muitas matérias do IME se mostraram importantes para nosso projeto, dentre elas:

    Projetos Futuros

    Todos nós pretendemos terminar o trabalho iniciado tanto no Patto quanto no Gannso e também temos em mente um novo projeto, envolvendo a expansão da MIP para comportar gráficos 3D, e um provável jogo utilizando esse novo módulo.


    (1) Alpha-Blending: sejam duas cores RGB c1 = (r1, g1, b1) e c2 = (r2, g2, b2). O alpha-blending delas é uma função F(c1, c2, a), para 0 <= a <= 255, é definida como:

    Isto é, uma média ponderada das componentes de c1 e c2.

    (2) Lighten: sejam duas cores RGB c1 = (r1, g1, b1) e c2 = (r2, g2, b2). O lighten delas é uma função F(c1, c2) definida como:

    F(c1, c2) = (max(r1, r2), max(g1, g2), max(b1, b2))