Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
Bruno Bottino Ferreira e Yanko Gitahy Oliveira
Dystopia e SumoCheckers
Rio de Janeiro - RJ, Brasil
3 de março de 2011
Bruno Bottino Ferreira e Yanko Gitahy Oliveira
Dystopia e SumoCheckers
Monografia apresentada para obtenção do Graude Bacharel em Ciência da Computação pelaUniversidade Federal do Rio de Janeiro.
Orientador:
Adriano Joaquim de Oliveira Cruz
INSTITUTO DE MATEMÁTICA
CENTRO DE CIÊNCIAS MATEMÁTICAS E DA NATUREZA
UNIVERSIDADE FEDERAL DO RIO DE JANEIRO
Rio de Janeiro - RJ, Brasil
3 de março de 2011
Monografia de Projeto Final de Graduação sob o título “Dystopia e SumoCheckers”, de-
fendida por Bruno Bottino Ferreira e Yanko Gitahy Oliveira e aprovada em 3 de março de
2011, no Rio de Janeiro, Estado do Rio de Janeiro, pela banca examinadora constituída pelos
professores:
Prof. Ph.D. Adriano J. de O. CruzOrientador
Prof. D.Sc. João Carlos Pereira da SilvaUniversidade Federal do Rio de Janeiro
Prof. D.Sc. Geraldo Bonorino XexéoUniversidade Federal do Rio de Janeiro
Resumo
Este trabalho destina-se a documentar o desenvolvimento do arcabouço Dystopia, desti-nado a auxiliar no desenvolvimento de jogos eletrônicos e aplicações interativas em geral, queteve como focos principais a organização, reusabilidade e o baixo custo, além do requisito deatender a múltiplas plataformas. É utilizado como exemplo o SumoCheckers, jogo eletrônicoimplementado utilizando este arcabouço, adicionado de bibliotecas disponíveis gratuitamentepara uso não-comercial. Também são abordados conceitos de game design e planejamento,estudando as diferentes influências presentes na criação de um jogo eletrônico.
Abstract
The present work intends to document the development of the Dystopia framework, usedfor making video games and interactive applications in general, having as its main goals codeorganization, reusability and low cost, and also attending the requirement of being cross plat-form. SumoCheckers, a game implemented using the framework and available free-for-non-commercial-use libraries, is used as an example. Concepts of game design and planning arealso discussed, studying the different influences in the creation of a video game.
Dedicatória
Dedico este trabalho à minha mãe Elizabeth, que me ensinou a ter caráter e integridade;
ao meu pai Marcus André, que me ensinou que com força de vontade qualquer um pode chegar
onde quiser; à minha irmã Barbara, por ter estado ao meu lado e apoiando meus projetos desde
sempre; e à minha namorada, Débora Andrade, por tudo que já passamos e ainda passaremos
juntos.
- Bruno Bottino Ferreira
Este trabalho é dedicado a meus pais Osmar e Regina, irmãos Yuri e Yane, minha namorada
Erika M. Z. K. Piffer e à Confraria, aqueles que sempre me apoiaram nessa longa jornada de
muitos primeiros passos.
- Yanko Gitahy Oliveira
Agradecimentos
Agradecemos principalmente ao professor Adriano Cruz pelo apoio e pela liberdade de
pesquisa;
aos nossos colegas do Laboratório de Inteligência Computacional pelo apoio constante e
auxílio nos momentos necessários;
aos nossos amigos que ouviram falar, e jogaram, SumoCheckers até dizerem “chega”;
a Steve "Sinbad" Streeting e toda a equipe de desenvolvimento pela OGRE3D;
a toda a comunidade OpenSource e criadores de middlewares acessíveis por tornarem o
mundo um lugar mais fácil para indies;
a Yuji Naka, Ron Gilbert, John Carmack e Gabe Newell.
Sumário
Lista de Figuras
1 Introdução p. 13
1.1 Motivação e objetivos deste trabalho . . . . . . . . . . . . . . . . . . . . . . p. 14
1.2 Contextualizacão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 15
1.3 Estrutura da monografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 16
2 O arcabouço: Dystopia p. 17
2.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 18
2.2 Arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 18
2.2.1 Wrappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 19
2.2.1.1 Entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 20
2.2.1.2 Rede . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 20
2.2.1.3 Áudio . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 22
2.2.2 Módulos diretos . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 22
2.2.2.1 Controle de tempo . . . . . . . . . . . . . . . . . . . . . . p. 23
2.2.2.2 Máquina de estados finitos . . . . . . . . . . . . . . . . . p. 23
2.2.2.3 Aplicação Orientada a Objetos . . . . . . . . . . . . . . . p. 25
2.2.2.4 Cálculo Vetorial e Geometria Analítica: Vetores 2D e 3D . p. 26
2.2.2.5 GameObject . . . . . . . . . . . . . . . . . . . . . . . . . p. 26
2.2.2.6 Tratamento de erros . . . . . . . . . . . . . . . . . . . . . p. 28
2.3 Detalhes da Implementação . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 28
2.3.1 Wrappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 28
2.3.1.1 Áudio . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 29
2.3.1.2 Rede . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 30
2.3.1.3 Entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 32
2.3.2 Integração com OGRE3D . . . . . . . . . . . . . . . . . . . . . . . p. 33
2.3.2.1 Aplicação Orientada e Objetos OGRE . . . . . . . . . . . p. 34
2.3.2.2 Integração com o módulo de CVGA . . . . . . . . . . . . p. 34
2.3.2.3 Integração com GameObject . . . . . . . . . . . . . . . . p. 34
2.3.2.4 Integração com o módulo de Áudio . . . . . . . . . . . . . p. 35
2.3.2.5 Módulos auxiliares usando OGRE . . . . . . . . . . . . . p. 35
2.3.3 Questões multiplataforma . . . . . . . . . . . . . . . . . . . . . . . p. 36
3 O jogo: SumoCheckers p. 37
3.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 38
3.2 Protótipo Inicial (2D) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 38
3.2.1 Bibliotecas externas utilizadas . . . . . . . . . . . . . . . . . . . . . p. 40
3.2.2 Experimentos com Inteligência Artificial . . . . . . . . . . . . . . . p. 41
3.2.3 Experimentos com jogo em rede e Internet . . . . . . . . . . . . . . p. 43
3.3 Versão 3D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 44
3.3.1 Conteúdo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 45
3.3.2 Efeitos e melhorias visuais . . . . . . . . . . . . . . . . . . . . . . . p. 47
3.3.3 Adaptação da lógica de jogo . . . . . . . . . . . . . . . . . . . . . . p. 49
3.3.4 Playtests e mudanças no gameplay . . . . . . . . . . . . . . . . . . . p. 51
4 Conclusões e trabalhos futuros p. 53
Referências p. 56
Anexo A -- Documentação das classes do Dystopia p. 58
A.1 Referência da Classe Dystopia::Application . . . . . . . . . . . . . . . . . . p. 58
Métodos Públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 58
A.1.1 Descrição Detalhada . . . . . . . . . . . . . . . . . . . . . . . . . . p. 59
A.1.2 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 59
A.1.2.1 afterSetup . . . . . . . . . . . . . . . . . . . . . . . . . . p. 59
A.1.2.2 frameUpdate . . . . . . . . . . . . . . . . . . . . . . . . . p. 59
A.1.2.3 getWindowTitle . . . . . . . . . . . . . . . . . . . . . . . p. 59
A.2 Referência da Classe Dystopia::InputManager . . . . . . . . . . . . . . . . . p. 59
Métodos Públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.1 Descrição Detalhada . . . . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2.1 addJoystickListener . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2.2 addKeyListener . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2.3 addMouseListener . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2.4 setWindowSize . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.2.2.5 update . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 60
A.3 Referência da Classe Dystopia::NetworkInterface . . . . . . . . . . . . . . . p. 61
Métodos Públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 61
A.3.1 Descrição Detalhada . . . . . . . . . . . . . . . . . . . . . . . . . . p. 61
A.3.2 Construtores & Destrutores . . . . . . . . . . . . . . . . . . . . . . . p. 61
A.3.2.1 NetworkInterface . . . . . . . . . . . . . . . . . . . . . . p. 61
A.3.3 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.1 freePacket . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.2 getNumberClients . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.3 pollPacket . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.4 send . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.5 shutdown . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.6 startup . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.7 startup . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 62
A.3.3.8 update . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 63
A.4 Referência da Classe Dystopia::SoundManager . . . . . . . . . . . . . . . . p. 63
Métodos Públicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 63
A.4.1 Descrição Detalhada . . . . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2.1 attachListenerToOgreCamera . . . . . . . . . . . . . . . . p. 64
A.4.2.2 createEmitter . . . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2.3 flushAllSounds . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2.4 getListenerLook . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2.5 getListenerPos . . . . . . . . . . . . . . . . . . . . . . . . p. 64
A.4.2.6 getListenerUp . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.7 getListenerVel . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.8 getSoundId . . . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.9 getWorldScale . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.10 loadSound . . . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.11 setListenerLook . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.12 setListenerPos . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.13 setListenerUp . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.14 setListenerVel . . . . . . . . . . . . . . . . . . . . . . . . p. 65
A.4.2.15 setupAudio . . . . . . . . . . . . . . . . . . . . . . . . . . p. 66
A.4.2.16 setWorldScale . . . . . . . . . . . . . . . . . . . . . . . . p. 66
A.4.2.17 unloadSound . . . . . . . . . . . . . . . . . . . . . . . . . p. 66
A.4.2.18 unloadSound . . . . . . . . . . . . . . . . . . . . . . . . . p. 66
Lista de Figuras
1 Arquitetura geral do Dystopia . . . . . . . . . . . . . . . . . . . . . . . . . p. 19
2 Aparência do primeiro protótipo do SumoCheckers . . . . . . . . . . . . . . p. 39
3 Segunda versão dos gráficos do SumoCheckers . . . . . . . . . . . . . . . . p. 41
4 Aparência da versão 3D do SumoCheckers . . . . . . . . . . . . . . . . . . . p. 46
5 Técnicas utilizadas para criação do cenário do jogo . . . . . . . . . . . . . . p. 48
6 Comparação exemplificando a utilização da técnica de Normal Mapping . . . p. 50
13
1 Introdução
“All your base are belong to us.”
CATS (Zero Wing)
Neste capítulo são apresentados o objetivo desta monografia e a estrutura da mesma.
1.1 Motivação e objetivos deste trabalho 14
1.1 Motivação e objetivos deste trabalho
A área de jogos eletrônicos é inerentemente ligada à Ciência da Computação, evocando
conhecimento não só de linguagens de programação, mas também de estruturas de dados e al-
goritmos, programação paralela, inteligência artificial e computacional, redes de computadores,
interface humano-computador, além de computação gráfica[1, 2]. Com o alcance de parcelas
cada vez maiores da população a computadores pessoais, videogames e instalações interativas,
faz-se necessário o estudo de métodos e técnicas para a implementação destes tipos de sistemas.
Para tal, tem-se dois caminhos comumente seguidos: ou licencia-se tecnologia proprietária que,
quando tem preços acessíveis para o uso, o acesso ao código fonte tem preço proibitivo, ou
utiliza-se bibliotecas de código aberto, mas que normalmente são muito especificas e pouco
flexíveis.
Além disso, frequentemente há a necessidade em laboratórios de pesquisa de um ambiente
virtual para experimentos, sendo necessária uma plataforma já existente para tal; uma imple-
mentação do zero não só tomaria tempo, como fugiria ao foco das pesquisas. Montando-se um
sistema facilmente reutilizável, diversos projetos poderiam tê-lo como base, sendo feitas apenas
as modificações estritamente necessárias para sua utilização.
Finalmente, temos o fator de aprendizado: o melhor meio de entender como funciona um
sistema é de fato construí-lo, pesquisando e pesando diferentes possibilidades com melhor cus-
to/benefício tanto na estrutura geral quanto em seus componentes. Tendo em mente esses três
fatores (baixo custo, reusabilidade e possibilidade de pesquisa), foi criado o Dystopia, arca-
bouço que vem sendo desenvolvido desde 2007, tendo como objetivo maior criar um ambiente
para implementação rápida de projetos interativos sem a necessidade de licenciamento de tec-
nologia.
Este trabalho tem como objetivos analisar o planejamento e a implementação do arcabouço
Dystopia, assim como do primeiro projeto que o utiliza, um jogo eletrônico chamado Sumo-
Checkers. O foco maior será dado à implementação, mas serão abordados também temas di-
retamente relacionados ao jogo, como decisões de game design e jogabilidade, que acabam se
mostrando intimamente ligadas às escolhas de tecnologia e direção geral de um arcabouço para
aplicações interativas.
1.2 Contextualizacão 15
1.2 Contextualizacão
Todo jogo eletrônico possui uma engine, ou seja, um "motor" responsável por controlar
todas as partes envolvidas (vídeo, áudio, estruturas de dados, fases, personagens etc) na sua
execução. Estas normalmente são bem específicas sobre seu funcionamento: um jogo de estra-
tégia, por exemplo, não será feito utilizando uma engine para jogos de tiro em primeira pessoa;
apesar de tecnicamente possível, o retrabalho envolvido é muito grande. Sendo assim, é fácil
encontrar diversas possibilidades para jogos de estilos específicos, com soluções específicas
para aqueles tipos de cenário.
Mais raras, no entanto, são soluções que tentam ser abertas o suficiente para englobar qual-
quer tipo de jogo sem grandes necessidades de modificações–não só porque é mais complicado
manter uma arquitetura mais geral, mas porque o tempo necessário para uma implementação
deste tipo é grande. Sendo assim, a maioria destas engines generalistas é comercial: dois
exemplos atuais são a Unity3d e a Unreal Engine. Embora recentemente ambas tenham se
tornado muito mais acessiveis a desenvolvedores pequenos (tendo versões gratuitas para uso
não-comercial) temos alguns problemas. Primeiro, o acesso ao código fonte requer uma licença
especial (no caso da Unreal Engine, por exemplo, estima-se um custo de US$700.000,00). Disso
deriva-se o fato de que não é possível deixar de lado os recursos mais avançados no código do
programa, mesmo que estes não sejam utilizados. Finalmente, cada uma tem um fluxo de tra-
balho bem específico, ou seja, é preciso aprender o mesmo para a realização de um projeto.
Tendo em vista estas limitações, o foco do arcabouço é justamente apresentar uma base
para faciltar a implementação de uma engine própria do usuário. Para uma maior facilidade, fo-
ram selecionadas bibliotecas para compor um conjunto "padrão", delegando o trabalho mínimo
necessário à implementação do código específico da aplicação que se deseja criar, mas sempre
mantendo-se a possibilidade de trocar facilmente estas bibliotecas por outras, ou até mesmo não
utilizar certos módulos.
Outras idéias importantes para o entendimento completo desta monografia são ligados à
Ludologia, ou seja, o estudo dos jogos (sejam eles eletrônicos ou não). Os conceitos de game-
play e rejogabilidade são especialmente importantes: um diz respeito às regras e mecânicas dos
jogos, ou seja, como funciona o jogo em si, ignorando-se gráficos, história e demais formas de
conteúdo. O segundo referencia a idéia de "quantas vezes um jogador joga um jogo", ou seja,
é uma medida da qualidade ligada, principalmente, ao gameplay, mas que é influenciada por
todos os outros fatores envolvidos. Para um jogo atingir sucesso e longevidade, o foco nesses
dois fatores é especialmente importante.
1.3 Estrutura da monografia 16
1.3 Estrutura da monografia
No capítulo 2 é descrita a estrutura do arcabouço desenvolvido e cada um de seus compo-
nentes.
No capítulo 3 é descrita a mecânica do jogo desenvolvido e alguns detalhes de sua imple-
mentação.
No capítulo 4 são apresentadas as conclusões deste trabalho e sugestões para trabalhos
futuros.
17
2 O arcabouço: Dystopia
“You’ll need something of the thread, something of the head, something of the body, and
something of the dead.”
Voodoo Lady (Monkey Island 2: LeChuck’s Revenge)
Neste capítulo são apresentados a estrutura básica do arcabouço desenvolvido, sua arquite-
tura e detalhes da implementação de cada um dos módulos.
2.1 Introdução 18
2.1 Introdução
O arcabouço Dystopia foi pensado e desenvolvido como um conjunto de classes na lingua-
gem C++, para auxiliar na criação de jogos eletrônicos e aplicativos interativos relacionados.
A arquitetura do Dystopia foi definida de maneira a atender alguns objetivos gerais impor-
tantes:
• Facilidade de Uso: objetivo principal e mais óbvio, as interfaces providas pelo framework
devem ser de uma dificuldade menor do que a alternativa de acesso direto ao hardware,
sistema operacional ou biblioteca especifica;
• Modularidade: os componentes do framework podem ser usados separadamente, ou em
conjunto; o programador poderá optar por utilizar quaisquer módulos dentre os existentes,
sem prejuízo à sua funcionalidade individual. Ainda que possa existir alguma integração
entre os diferentes módulos, o código que implementar tal integração deverá ser condici-
onado à presença de todos os módulos respectivos;
• Portabilidade: os componentes devem funcionar na maior quantidade de plataformas pos-
sível. Sempre que aplicável, o código deve ser escrito de uma única maneira que funcione
de maneira portável; onde isso não for possível, implementações condicionais relativas a
cada arquitetura alvo serão feitas;
• Abstração: relacionado à portabilidade, as interfaces devem ser apresentadas de uma
maneira consistente e alheia à arquitetura e Sistema Operacional presentes no momento
da utilização.
2.2 Arquitetura
Em uma primeira instância, pode-se considerar que o código do framework consiste de
alguns módulos bem definidos, separados em dois tipos básicos:
• Wrappers: expõem uma interface comum para acesso a alguma funcionalidade imple-
mentada por bibliotecas externas. É o caso dos módulos de entrada, rede e som;
• Módulos diretos: implementações completas e isentas de dependências externas, relaci-
onadas a alguma funcionalidade pertinente ao desenvolvimento de jogos. É o caso dos
módulos de controle de tempo, máquina de estados, AppMain, vetores 2D e 3D e Game-
Object.
2.2 Arquitetura 19
Figura 1: Arquitetura geral do Dystopia
2.2.1 Wrappers
Foram definidos wrappers para três tipos de funcionalidades: entrada, rede e áudio. Foi
decidido que, a princípio, não seria feito um wrapper para biblioteca gráfica porque cada bibli-
oteca costuma ter um conjunto bem específico de funcionalidades, além de existirem bibliotecas
gráficas 2D e 3D que já são multiplataforma e simples o suficiente para uso direto. Apesar disso,
foram desenvolvidos alguns pequenos módulos de suporte à biblioteca OGRE3D[3], conforme
pode ser visto na seção 2.3.2.
Os wrappers de entrada, áudio e rede no Dystopia são chamados, respectivamente, de Input-
Manager, SoundManager e NetworkInterface. Foi decidido chamar de “Manager” os wrappers
de entrada e áudio porque, em geral, só deve existir uma instância de cada um destes (ou seja,
eles implementam o design pattern[4] “Singleton”); por sua vez, deve-se usar uma instância
de NetworkInterface para cada conexão de rede aberta em um dado momento, podendo haver
várias conexões abertas ao mesmo tempo na mesma aplicação (daí "Interface").
Vale lembrar que os nomes e as funcionalidades descritos nesta seção são relativos às clas-
ses abstratas que representam cada um dos wrappers, sendo as implementações de fato con-
templadas por classes derivadas destas, utilizando bibliotecas externas. Mais detalhes sobre as
implementações e exemplos de utilização podem ser encontrados na seção 2.3.1.
2.2 Arquitetura 20
2.2.1.1 Entrada
O módulo de entrada provê funcionalidade de eventos de entrada de teclado, mouse e joys-
tick. Os eventos são comunicados à aplicação por meio de callbacks em listeners. Ou seja, uma
classe da aplicação implementa uma interface programática predefinida e se registra com o mó-
dulo de entrada para receber certos tipos de eventos. Cada tipo de dispositivo possui uma dessas
interfaces, sendo a mesma responsável pelos métodos relativos a cada tipo de evento disparado
por ele. Os tipos de eventos recebidos para cada tipo de dispositivo são como a seguir:
• Teclado: tecla pressionada (KeyPressed) e tecla levantada (KeyReleased);
• Mouse: movimento (MouseMoved), botão pressionado (MousePressed) e botão levantado
(MouseReleased).
• JoyStick: movimento do eixo (AxisMoved), botão pressionado (ButtonPressed) e botão
levantado (ButtonReleased).
Cada tipo de evento tem um conjunto de informações associado, contendo um identificador
numérico do dispositivo que gerou o evento (caso haja mais de um do mesmo tipo instalado no
sistema), um identificador do botão, tecla ou eixo que gerou o evento em questão e, no caso de
mouse e joystick, o estado completo de todos os botões e eixos do dispositivo naquele momento.
2.2.1.2 Rede
Uma característica cada vez mais comum nos jogos eletrônicos é a capacidade de se jogar
com outras pessoas, seja em rede local ou ao redor do mundo, através da Internet. Para im-
plementar este tipo de funcionalidade, é necessário uma robusta infra-estrutura, que cuide dos
detalhes dos protocolos de comunicação entre computadores e exponha ao desenvolvedor de ga-
meplay apenas o necessário para que ele possa implementar a comunicação entre os elementos
do jogo de uma maneira simples e funcional.
Sendo assim, foi definido que a interface de rede do Dystopia seria o mais simples possível,
a princípio, focando apenas em enviar e receber pacotes utilizando o paradigma cliente-servidor.
Para abrir uma porta de rede para comunicação, basta instanciar e inicializar um NetworkInter-
face, passando apenas os dados essenciais para estabelecimento da conexão: o papel da interface
na comunicação (cliente ou servidor), e:
• No caso do servidor, a porta a ser utilizada, que será aberta localmente, e a quantidade
máxima de clientes suportada;
2.2 Arquitetura 21
• No caso do cliente, a porta a ser procurada para conexão remota ao servidor e o endereço
do servidor remoto.
É importante salientar novamente que mais de uma interface deste tipo pode existir ao mesmo
tempo, sendo inclusive possível clientes e servidores comunicando-se entre si na mesma má-
quina. Para isto, basta que os clientes estabeleçam uma conexão de loopback para “localhost”
ou “127.0.0.1”.
Uma vez inicializado, o servidor está pronto para funcionar. O cliente, porém, precisa espe-
rar a resposta do servidor para que possa enviar e receber pacotes. Sendo assim, o método que
inicializa o cliente retorna imediatamente, mas isso não significa que foi estabelecida uma co-
nexão; o primeiro valor de retorno do método “update” do cliente determinará se uma conexão
foi estabelecida com sucesso ou não.
Após verificado que uma conexão de sucesso foi estabelecida ou, no caso do servidor, assim
que a porta foi aberta com sucesso, a aplicação deve, a intervalos regulares (por exemplo, a cada
passo da simulação):
• Chamar o método “update” da interface. Este método retorna uma série de valores in-
dicando um erro na conexão, ou um valor indicando que a conexão ainda está ativa e
pacotes podem ou não terem sido recebidos. Para processar estes pacotes, deve-se fazer
um loop de chamadas ao método abaixo;
• Chamar o método “pollPacket” da interface para receber um pacote pendente, passando
um ponteiro para uma estrutura de pacote, até que este método retorne FALSE, indicando
que não há mais pacotes pendentes;
• Realizar o processamento adequado do pacote recebido, de acordo com a lógica da apli-
cação;
• Chamar o método “freePacket” passando como parâmetro a estrutura de pacote preen-
chida, para que recursos internos sejam liberados.
Além disso, a qualquer momento a aplicação pode enviar um pacote de tamanho arbitrário,
indicando, se for o servidor, se ela deve ser enviada a todos os clientes e, caso contrario, também
o cliente destino da mensagem. Por outro lado, o cliente só pode enviar um pacote ao servidor
e não a outros clientes.
2.2 Arquitetura 22
2.2.1.3 Áudio
Para aplicações interativas, não somente jogos eletrônicos, serem bem sucedidas, é neces-
sário que o usuário consiga abstrair-se ao máximo em relação à interface, sentindo-se “imerso"
no universo da aplicação no mínimo o suficiente para que tenha interesse em utilizá-la. Desta
forma, é essencial a presença de ambientação auditiva, na forma de efeitos sonoros e música.
No mundo real, alguns fatores afetam fortemente a maneira como percebemos os sons, em
especial a posição e velocidade do ouvinte em relação à origem do som (atenuando o volume
percebido e causando o chamado efeito Doppler) e as características do ambiente onde ele é
propagado – a maneira como um som se propaga num campo aberto não é a mesma que em
uma caverna, por exemplo. Sendo assim, de maneira geral as bibliotecas de áudio expõem
funcionalidades para lidar com isto de alguma forma.
A princípio, foi implementado apenas o conceito de posição e velocidade do ouvinte e
do emissor através de um paradigma baseado em orientação a objetos. Enquanto os atributos
do ouvinte (posição e velocidade) são especificados diretamente no SoundManager, é preciso
instanciar um objeto emissor (SoundEmitter) para tocar um som posicional. Este emissor possui
atributos de posição e velocidade, assim como o ouvinte, e métodos que permitem tocar sons.
As características de efeitos sonoros tocados por um emissor serão afetadas pelos atributos
deste, mesmo que ele se mova enquanto o som está sendo tocado. Existem também métodos
para reprodução de sons sem informação posicional, definidos diretamente no SoundManager.
Em aplicações reais, geralmente um emissor está associado a um objeto do mundo do jogo
(GameObject) e o ouvinte acompanha a posição da câmera. Alguns funcionalidades imple-
mentadas pelo framework que podem auxiliar bastante neste tipo de utilização estão descritos,
respectivamente, nas seções 2.2.2.5 e 2.3.2.
Por último, mas não menos importante, os componentes vitais necessários para uma am-
bientação acústica bem sucedida são as amostras sonoras (sejam efeitos sonoros ou músicas).
Logo, é necessária uma funcionalidade para carregar e gerenciar sons de maneira prática. No
Dystopia, um som carregado de um arquivo possui um ID numérico e um nome, podendo ser
referenciado por qualquer destes em todos os métodos de reprodução.
2.2.2 Módulos diretos
Além dos módulos wrappers do framework, foram desenvolvidas algumas funcionalidades
auxiliares com implementações diretas, sem utilização de bibliotecas externas. Estas funcionali-
2.2 Arquitetura 23
dades em geral visam diminuir a quantidade de retrabalho sempre presente no desenvolvimento
de jogos, que evocam a utilização recorrente de algumas estruturas de dados e de controle de
execução. Em alguns casos menores, o objetivo destes módulos foi encapsular chamadas ao
sistema operacional da máquina hospedeira, além de auxiliar no processo de desenvolvimento
em si.
2.2.2.1 Controle de tempo
Foi desenvolvido um pequeno módulo para obtenção de informações de tempo e delay
artificial, por meio de funções estáticas. Além de uma função para causar um delay artificial
de até um segundo (Sleep), foram criadas funções para retornar a quantidade de segundos,
milissegundos e microssegundos desde algum momento inicial (que, dependendo do SO, pode
ser o momento de boot da máquina ou o momento em que o programa começou a ser executado).
Como os valores retornados por este módulo são sempre utilizados relativos uns aos outros, a
referência inicial da contagem é irrelevante desde que seja mantida entre os valores a serem
comparados.
A implementação deste pequeno módulo busca encapsular chamadas ao sistema operacio-
nal ou a outras bibliotecas que já estejam sendo utilizadas para outras funcionalidades, sempre
da maneira que retorne a maior precisão possível. As chamadas dependem de quais bibliote-
cas estejam definidas para utilização, recaindo a chamadas diretas ao SO apenas caso nenhuma
biblioteca que disponha de funções de controle de tempo esteja presente no momento da com-
pilação.
2.2.2.2 Máquina de estados finitos
Uma estrutura de controle de fluxo extremamente recorrente e quase onipresente em jogos
eletrônicos é a Máquina de Estados Finitos (Finite State Machine–FSM). Como o fluxo de
execução de um jogo em geral depende muito da situação atual (por exemplo: introdução,
menu, cutscene, exploração, batalha, pausa), estas situações podem ser modeladas como estados
distintos em uma máquina de estados, onde a cada passo de atualização o estado pode requisitar
que seja modificado para algum outro (por exemplo: ao apertar START durante a introdução, vá
para o menu).
Outro campo onde FSM’s são bastante utilizadas é a Inteligência Artificial, para modelar
o comportamento de um agente autônomo ao longo do tempo. Um inimigo poderia, digamos,
estar constantemente no estado PATRULHANDO até que o jogador se aproxime o suficiente e o
2.2 Arquitetura 24
estado do inimigo mude para ATACANDO.
Em algumas situações, pode ser interessante que haja alguma memória a respeito do his-
tórico de estados visitados. Em especial, um estado pode ser chamado a partir de mais de um
outro, e requisitar que seja retornado para o anterior, independentemente de qual fosse. Por
isso, frequentemente é implementada uma pilha de estados que representa o conceito abstrato
de estados hierárquicos. Além disso, um estado em si pode conter outra máquina de estados,
para criar comportamentos tão complexos quanto se queira.
No Dystopia, foi implementado uma classe de Máquina de Estados Finitos com pilha de
estados. Cada estado deve implementar um método “update” que realiza seu trabalho por um
passo e retorna a transição desejada, que pode ser PUSH, para empilhar um novo estado e mudar
para ele; POP, para desempilhar o estado atual e mudar para o próximo na pilha; CHANGE, para
trocar o estado atual por outro sem modificar a pilha de estados; ou nenhuma, para que o estado
atual continue sendo o mesmo na próxima atualização.
A máquina de estados possui um método “update” que deve ser chamado a cada passo da
aplicação, e repassa esta chamada para o estado no topo da pilha. Além disso, implementa todas
as interfaces de Listener do sistema de entrada, repassando os eventos para o estado atual. Desta
forma, após inicializar o sistema de entrada, basta registrar uma máquina de estados para rece-
ber os tipos de eventos desejados, sendo estes automaticamente enviados para processamento
no estado atual, desde que ele implemente os métodos receptores correspondentes (para mais
informações, veja a seção 2.2.1.1).
// Exemplo de utilização da máquina de estados
class MyState: public Dystopia::State
{
public:
std::pair<Dystopia::StateTransition, Dystopia::State*>
update(void) {
if(manter_estado)
return make_pair(STATE_CHANGE, this);
else if(mudar_estado)
return make_pair(STATE_CHANGE, new Outro());
else if(empilhar_estado)
return make_pair(STATE_PUSH, new Outro());
else if(desempilhar_estado)
return make_pair(STATE_POP, NULL);
2.2 Arquitetura 25
}
};
Dystopia::StateMachine states;
states.push(new MyState());
//durante o loop principal...
//MyState::update será chamado automaticamente
states.update();
2.2.2.3 Aplicação Orientada a Objetos
Programas escritos em C ou C++ tradicionalmente têm seu ponto de entrada em uma fun-
ção “main”. Porém, esta abordagem não condiz muito bem com o paradigma de orientação a
objetos almejado no design do Dystopia. Por isso, foi criado um módulo que implementa a
função “main” necessária para ser utilizada como ponto de entrada da aplicação pelo SO, e uma
pequena classe abstrata chamada Application que define alguns métodos vazios, chamados em
momentos oportunos, como “go” e “abort” (chamados na inicialização e finalização, respecti-
vamente). Esta classe também implementa listeners para todos os tipos de eventos de entrada,
e deve ser herdada e incrementada para ser usada de fato.
Porém, o objetivo maior é que esta classe seja especializada para tipos específicos de bibli-
otecas gráficas, de modo que possa encapsular a criação da janela e outros detalhes da inicia-
lização visual e do sistema de entrada (que necessita de um handle para a janela do programa
para ser inicializado). Além disso, nesta especialização pode-se criar a estrutura do loop prin-
cipal de acordo com a biblioteca gráfica utilizada, chamando o método abstrato “frameUpdate”
a cada quadro renderizado. Assim, uma aplicação precisa apenas herdar desta especialização
e implementar este método para ter uma janela pronta que chama um método de atualização a
cada quadro.
Uma especialização desta classe foi desenvolvida para a biblioteca OGRE3D, com várias
funcionalidades pertinentes. Uma descrição mais detalhada sobre esta especialização pode ser
encontrada na seção 2.3.2.1.
2.2 Arquitetura 26
2.2.2.4 Cálculo Vetorial e Geometria Analítica: Vetores 2D e 3D
Embora utilidade mais óbvia de um sistema de coordenadas em um jogo seja a exibição
gráfica e, por isso mesmo, a maioria das bibliotecas gráficas 3D (e mesmo algumas 2D) im-
plemente essa funcionalidade, quando se deseja um sistema altamente modularizado e flexível
deve haver um subsistema dedicado apenas a isso. Um erro recorrente em projetos mal planeja-
dos é utilizar as estruturas de coordenadas da tela ou espaço 3D para controlar todas as demais
partes do programa, causando pesadelos intermináveis em relação à portabilidade.
A informação posicional deve ser propagada a diversos outros módulos (por exemplo, o
de Áudio, como visto na seção 2.2.1.3). Além disso, a lógica de um jogo ou ambiente virtual
qualquer está quase sempre ligada às posições de seus objetos. Se todo este tráfego de informa-
ções vetoriais utilizar ferramentas de uma biblioteca gráfica específica, todo código que trabalhe
com informação posicional terá que ser retrabalhado caso seja necessário remover ou trocar tal
biblioteca. Por outro lado, se um sistema comum e unificado tiver todas as funcionalidades
necessárias, estas são implementadas uma única vez e criam-se conversões para quaisquer mó-
dulos específicos que necessitem, sendo possível mudar de módulos gráficos e até mesmo criar
uma aplicação sem interface visual (um servidor dedicado, por exemplo) sem nenhum tipo de
retrabalho.
O módulo de Cálculo Vetorial e Geometria Analítica (CVGA) no Dystopia é composto pe-
las classes Vec2 e Vec3, que implementam, respectivamente em 2D e 3D, operações tradicionais
de vetores, como soma, subtração, multiplicação por escalar, produto interno, produto vetorial,
normalização etc. Estas operações são implementadas utilizando-se sobrecarga de operadores
sempre que for possível e apropriado, de forma a ser intuitivo lidar com vetores utilizando-se
os símbolos habituais usados para tratar grandezas numéricas na linguagem C++.
Uma longa explicação sobre Geometria Analítica e Álgebra Linear para jogos, com exem-
plos de implementação e muito além do que o descrito aqui, pode ser encontrada em [5].
2.2.2.5 GameObject
Todo ambiente de um jogo eletrônico ou aplicação interativa é composto de elementos
estáticos e elementos dinâmicos. Como elementos estáticos, podemos citar principalmente os
componentes do cenário, fundo etc; tais elementos não mudam de estado ao longo da execução
do jogo–no máximo são criados, por exemplo, no começo de uma fase, e destruídos no final
desta. Por outro lado, elementos dinâmicos como o jogador, inimigos, itens, projéteis, sistemas
de particulas, além de objetos com os quais o jogador pode interagir, como alavancas, cordas
2.2 Arquitetura 27
e veículos, mudam de estado constantemente no universo do jogo, seja apenas mudando de
posição, ou mesmo tendo regras únicas e complexas que regem seu comportamento, como a IA
de um inimigo.
Enquanto na maioria das vezes pouca atenção precisa ser dada aos elementos estáticos,
já que eles interferem pouco no gameplay (exceto por questões de gerenciamento de visibili-
dade e verificação de colisões, para as quais existem várias soluções prontas e bem testadas,
primariamente com a utilização de técnicas de divisão espacial como árvores BSP, Octrees ou
KD-Trees[5]), elementos dinâmicos precisam ser bem controlados e gerenciados devido à sua
volatilidade, na forma de uma quantidade arbitrária de atributos que variam ao longo do tempo.
Por isso, muitas engines de jogos implementam o conceito de GameObject, que é uma repre-
sentação abstrata de um elemento dinâmico do jogo.
Este conceito pode ser implementado de várias maneiras diferentes, se dividindo em duas
categorias principais: centrada no objeto, e centrada nos atributos. No primeiro caso, cada ob-
jeto é representado por uma instância de uma classe na linguagem de programação do jogo,
possivelmente existindo uma hierarquia de classes representando vários tipos diferentes de ob-
jetos, sendo os atributos implementados pela própria classe. Já na segunda abordagem, cada
objeto é representado apenas por um identificador, que funciona como índice para um conjunto
de tabelas, uma para cada tipo de atributo existente.
Ambas abordagens têm vantagens e desvantagens que não serão discutidas aqui; uma dis-
cussão extensa sobre os diferentes paradigmas de gerenciamento de objetos pode ser vista em
[5]. O Dystopia implementa, em sua versão atual, apenas o paradigma centrado no objeto,
com uma única classe simples chamada GameObject, que simplifica a organização de alguns
atributos e comportamentos recorrentes. Esta classe encapsula, de acordo com os subsistemas
definidos para uso, os seguintes tipos de atributos (ou componentes):
• Um nome, implementado como um objeto string;
• Um emissor de som (SoundEmitter) que acompanha automaticamente a posição do objeto
(veja a seção 2.2.1.3);
• Uma entidade (que representa um modelo) da OGRE3D, juntamente com seu nó de posi-
ção (SceneNode; veja a seção 2.3.2.3);
• Uma posiçao 3D, representada por um objeto Vec3 nos métodos set e get, mas interna-
mente mapeada para as informações de posiçao do nó da OGRE, caso esteja definida para
uso, evitando redundância de memória.
2.3 Detalhes da Implementação 28
É importante salientar que as referências a objetos da OGRE3D só existem se o framework es-
tiver configurado para utilizar esta biblioteca, através da definição da constante correspondente
(veja a seção 2.3.2 para mais detalhes sobre a integração do Dystopia com OGRE3D). Caso
contrário, não existirá referência a nenhuma entidade ou modelo, e a posição 3D do objeto será
definida em termos de um Vec3 do Dystopia.
Esta abordagem também é considerada baseada em componentes, porque cada atributo
é apenas uma referência a um objeto de outro subsistema (gráfico, sonoro, física etc), e as
atualizações de cada um dos componentes são realizadas de fato nos passos de atualização de
cada um dos subsistemas, e não durante a atualização dos estados dos objetos.
2.2.2.6 Tratamento de erros
Dois pequenos mecanismos auxiliares de detecção e tratamento de erros foram criados no
framework. O primeiro deles é uma classe de exceção, contendo apenas uma descrição do erro.
Esta classe não chegou a ser utilizada em nenhum momento na implementação do Dystopia,
mas está diponível para alguma aplicação que queira utilizar-se dela.
O outro mecanismo implementado foi uma macro de assert (asserções). Esta macro visa
garantir que uma certa expressão, assumida verdadeira no momento do desenvolvimento, é
realmente verdadeira em tempo de execução. Caso ela não seja, a execução do programa é
interrompida e a asserção é reportada no console. Caso a constante _PRODUCTION esteja
definida, porém, a macro da asserção avalia para código vazio. Isto é feito para que as versões
finais da aplicação não contenham estas checagens, que podem causar uma queda desnecessária
na performance de execução. A estrutura básica desta macro, com poucas modificações, foi
retirada de [5].
2.3 Detalhes da Implementação
2.3.1 Wrappers
Com as definições das interfaces prontas, passamos a pesquisar quais seriam boas bibliote-
cas a serem usadas para iniciar o que seria a "implementação padrão" do arcabouço. Os únicos
requisitos eram que as bibliotecas fossem multiplataforma e gratuitas para uso não-comercial.
Considerou-se, portanto, tanto bibliotecas código aberto quanto código fechado. As seções a
seguir tratam da escolha das bibliotecas externas e alguns comentários a respeito de suas funci-
onalidades e adaptação ao Dystopia, além de exemplos de utilização de cara um dos módulos.
2.3 Detalhes da Implementação 29
2.3.1.1 Áudio
Foram consideradas três opções de bibliotecas de tratamento de áudio inicialmente:
• OpenAL[6]: criada com o intuito de ser análoga ao OpenGL, é utilizada como base para
diversas aplicações e engines de jogos. Até sua versão mais recente, era de código aberto,
sob licença LGPL, atualmente sendo propriedade da Creative Technology. Tem duas
versões alternativas, a original (que não é mais desenvolvida) e uma opção open-source
chamada OpenAL Soft, que ainda está sendo desenvolvida;
• FMOD[7]: a mais utilizada hoje em dia para jogos de grande porte, é uma solução propri-
etária completa, com editores e ferramentas. Gratuita para uso não comercial, mas com
uma licença cara (com investimento indo de US$3 mil a US$9 mil), sem caso especial
para orçamentos pequenos;
• IrrKlang[8]: código proprietário, com desenvolvimento ativo, simples e gratuita para uso
não comercial. A licença "pro" tem um preço relativamente acessível; para jogos que
sejam vendidos a menos de US$35,00, a licença "indie" custa apenas US$400,00, para
um número ilimitado de títulos.
Cruzando estas características, a que demonstrava melhor relação custo-benefício era a Irr-
Klang, sendo esta a escolhida para a implementação padrão. Assim, foi criada a IrrKlangIn-
terface, que seria responsável por implementar as especificações do Dystopia::SoundManager
utilizando os métodos desta biblioteca.
Como o mecanismo interno de gerenciamento e reprodução de sons da IrrKlang é bastante
próximo ao definido para o Dystopia, a implementação desta interface foi relativamente sim-
ples, requerendo cuidado apenas no reposicionamento constante dos sons tocados, já que esta
biblioteca não implementa o conceito de “emissor”, mas sim uma referência para cada som
sendo tocado, que pode ser utilizada para mudar seus atributos dinamicamente.
// Exemplo de utilização do SoundManager com IrrKlang.
//Inicialização
Dystopia::SoundManager *soundManager;
soundManager = new Dystopia::IrrKlangInterface();
soundManager->setupAudio();
//Carregar sons
2.3 Detalhes da Implementação 30
soundManager->loadSound("som1.wav", "som #1");
soundManager->loadSound("musica.ogg", "musica de fundo");
//Executar uma música (sem informacão posicional)
soundManager->play("musica de fundo", true);
//Alterar propriedades do ouvinte
soundManager->setListenerPos(listener_pos);
soundManager->setListenerVel(listener_vel);
//Criar um emissor e alterar suas propriedades
Dystopia::SoundEmitter *emitter;
emitter = soundManager->createEmitter();
emitter->setPosition(emitter_pos);
emitter->setVelocity(emitter_vel);
//Executar um som posicional
emitter->play("som #1");
2.3.1.2 Rede
Uma excelente solução para redes é a RakNet[9]. Seu código é fechado mas, além de
sua robustez, as licenças são altamente acessíveis: se o orçamento do projeto onde será utili-
zada é inferior a US$100.000,00, a biblioteca pode ser utilizada gratuitamente. Além disso, é
amplamente utilizada pela indústria, inclusive em títulos de produtoras de grande porte, sem
mencionar o suporte constante e atualizações freqüentes. Assim, foi uma escolha imediata.
Assim como no módulo de Áudio, o funcionamento da RakNet é bastante parecido com a
especificação da interface equivalente, com a maior parte dos comandos apenas repassados, com
duas ressalvas. A primeira é que muitas das chamadas foram simplificadas: como a RakNet
é uma biblioteca extremamente completa e extensa, disponibilizando várias maneiras de se
realizar a comunicação, muitos parâmetros foram fixados internamente para expor apenas uma
interface simples.
A outra ressalva diz respeito à fila de pacotes recebidos por uma máquina. Embora a
NetworkInterface exponha uma funcionalidade de fila idêntica à RakNet, foi implementada
uma fila interna para filtrar os “pacotes virtuais” gerados pela RakNet, como mensagens de
2.3 Detalhes da Implementação 31
erro e gerenciamento de conexão, de forma a serem devidamente tratadas pela interface e não
repassados para a fila de pacotes da aplicação.
// Exemplo de utilização do NetworkInterface com RakNet.
#define PORT_NUMBER 35123
//Servidor: inicialização
Dystopia::NetworkInterface *server;
server = new RakNetInterface(Dystopia::NETWORK_SERVER, PORT_NUMBER);
server->startup(max_clients);
//Cliente: inicialização
//Pode ser passado um domínio ou endereço IP.
Dystopia::NetworkInterface *client;
client = new RakNetInterface(Dystopia::NETWORK_CLIENT, PORT_NUMBER);
client->startup("my_server_address.coolservers.com");
bool connected = false;
bool loop = true;
while(!loop)
{
switch(client->update())
{
case NETWORK_SERVER_FULL:
cout << "Sorry, server is full." << endl;
loop = false;
break;
case NETWORK_CONNECTION_SUCCESS:
cout << "Connection success!" << endl;
connected = true;
loop = false;
break;
case NETWORK_CONNECTION_FAILED:
cout << "Connection failed." << endl;
loop = false;
2.3 Detalhes da Implementação 32
break;
}
Dystopia::Sleep(100);
}
//Servidor: recebendo pacotes e reenviando para todos
enum MyPackets {ID_CLIENT_TO_SERVER, ID_SERVER_TO_CLIENTS};
Dystopia::NetworkPacket* np;
server->update();
while(server->pollPacket(&np))
{
switch(np->id)
{
case ID_CLIENT_TO_SERVER:
server->send(ID_SERVER_TO_CLIENTS, //ID do pacote
np->len, //Tamanho dos dados
np->data, //Ponteiro para os dados
-1, //Cliente destino
true); //Enviar para todos
break;
}
}
2.3.1.3 Entrada
Para trabalhar com a entrada, escolhemos como padrão a OIS[10] (Object-Oriented Input
System). De código aberto e licença zlib, suporta Windows e Linux de maneira completa, e
possui “suporte parcial” a MacOS X. Consegue gerenciar entradas de mouse, teclado, joysticks
e até mesmo Wiimotes[11] nativamente.
Assim como nos casos de áudio e rede, o funcionamento da OIS é análogo à interface
definida no Dystopia: um objeto se registra com a biblioteca para receber tipos de eventos
dos dispositivos. Porém, neste caso os eventos são repassados diretamente à aplicação, apenas
passando por um processo de conversão de estruturas.
Um detalhe digno de nota em relação à implementação específica desta interface é relacio-
nado à sua portabilidade para outras plataformas, sendo coberto, portanto, na seção 2.3.3.
2.3 Detalhes da Implementação 33
// Exemplo de utilização do InputManager com OIS.
//Inicialização.
//"windowHnd" é o handle da janela da aplicação.
Dystopia::InputManager *inputManager;
inputManager = new OISInterface(windowHnd);
inputManager->setWindowSize(640, 480);
//Registra um objeto para receber os eventos de entrada.
//Supondo que "myListener" seja objeto de uma classe que
//herda de KeyListener, MouseListener e JoystickListener
m_inputManager->addKeyListener(myListener);
m_inputManager->addMouseListener(myListener);
m_inputManager->addJoystickListener(myListener);
//Exemplo de tratamento de eventos de teclado
void MyListener::keyPressed(const Dystopia::KeyEvent &arg)
{
if(arg.code == KC_X)
{
cout << "Apertei a tecla X!" << endl;
}
}
2.3.2 Integração com OGRE3D
Embora o framework não encapsule completamente o acesso a nenhuma biblioteca grá-
fica específica, o desenvolvimento de algumas funcionalidades para auxiliar a sua utilização
com a biblioteca OGRE3D[3] se mostrou bastante relevante, devido a tais funcionalidades se-
rem recorrentes e passíveis de encapsulamento parcial. Porém, diferentemente dos wrappers
(que encapsulam totalmente a utilização das bibliotecas externas), quase todas as referências
internas à OGRE utilizadas por estes componentes são compartilhadas com a aplicação; seja
criadas pelo framework e disponibilizadas a ela (como no caso da Aplicação Orientada e Obje-
tos OGRE), como no caso de requisitar referências à aplicação para seu funcionamento (como
o GameObject, sistema de áudio e os módulos auxiliares de splash e player de vídeo).
2.3 Detalhes da Implementação 34
É importante salientar também que esta biblioteca possui seu sistema próprio de Scene
Graph[12] (gerenciamento de cena) que cuida das posições de todas as entidades em um dado
momento. Não há nenhum tipo de gerenciamento de cena com objetos OGRE implementado
explicitamente no Dystopia, apenas referências a nós do Scene Graph, utilizados para controlar
(ou obter informações de) um objeto em especial, como no caso do GameObject.
Uma explicação sobre o funcionamento da biblioteca OGRE3D está além do escopo deste
trabalho, mas existem alguns livros que exploram em detalhes suas funcionalidades, como [13]
e [14], além de extensiva documentação (em forma de manual e wiki) e um fórum no website
da biblioteca[3].
2.3.2.1 Aplicação Orientada e Objetos OGRE
Foi criada uma especialização da classe Application (seção 2.2.2.3) para uso com OGRE,
contendo a criação da janela e inicialização da engine gráfica, gerenciamento do loop princi-
pal, inicialização de um InputManager com a biblioteca OIS (se definida para uso), criação de
um display com informações de debug no topo da tela (que pode ser posteriormente oculto) e
criação e gerenciamento dos movimentos de uma câmera de movimento livre (modo "noclip",
muito útil para situações de teste).
2.3.2.2 Integração com o módulo de CVGA
Como em OGRE, assim como no Dystopia, existem classes que representam vetores 2D
e 3D, foram implemendas conversões (em ambos os sentidos) para cada tipo de vetor. Desta
forma, um vetor Dystopia pode ser utilizado onde é pedido um vetor OGRE e vice-versa, au-
tomaticamente. Para isto, foi utilizada a funcionalidade de sobrecarga de operadores de C++;
como nesta linguagem um typecast (conversão de tipo) é considerado um operador, seu compor-
tamento pode ser sobrecarregado para tipos não-primitivos, de modo a implementar conversões
de tipo automáticas.
2.3.2.3 Integração com GameObject
A classe GameObject do Dystopia encapsula, como visto na seção 2.2.2.5, uma entidade
OGRE e seu respectivo SceneNode. Um SceneNode em OGRE é apenas um nó da árvore
do Scene Graph[12], e toda entidade precisa estar ligada a um SceneNode para ser visível na
cena. A posição e orientação de uma entidade são, então, determinadas pela concatenação das
transformações respectivas acumuladas no caminho desde a raiz do scene graph até o nó que a
2.3 Detalhes da Implementação 35
representa.
Durante a criação de um GameObject, a aplicação pode pedir que o SceneNode seja criado
no primeiro nível da árvore (ou seja, tenha como pai a raiz e suas transformações sejam abso-
lutas em relação à origem do espaço tridimensional), ou passar como parâmetro um SceneNode
que deve ser o pai do objeto na árvore do SceneGraph. Além disso, como pode existir mais
de uma cena em memória ao mesmo tempo, é necessário que a aplicação passe também uma
referência ao SceneManager (a representação em OGRE de um scene graph) onde o objeto
existirá.
2.3.2.4 Integração com o módulo de Áudio
Foram adicionados ao subsistema de áudio alguns métodos para automatizar a atualização
de posição e velocidade, tanto do ouvinte quanto dos emissores, além da orientação do ouvinte,
a partir de informações da OGRE. Pode-se definir que o ouvinte seja atualizado sempre por uma
câmera OGRE (já que, na prática, o ouvinte é sempre aquele que está visualizando o mundo
virtual), e que um emissor seja atualizado a partir da posição de um SceneNode OGRE.
2.3.2.5 Módulos auxiliares usando OGRE
Dois módulos pequenos que usam OGRE foram desenvolvidos para tarefas repetitivas re-
lacionadas à exibição de gráficos pré-renderizados:
• Tela de Espera (Splash Screen): desenha uma imagem em tela cheia, geralmente utilizada
na inicialização e em momentos em que recursos do jogo, como fases, estejam sendo
carregados;
• Player de Vídeo: reproduz um vídeo pré-renderizado de um arquivo para uma textura
OGRE. Esta textura poderá então ser usada juntamente com a tela de splash para fazer um
vídeo em tela cheia (FMV - Full Motion Video) ou aplicada em um modelo normal, como
uma textura animada. Além disso, se for passada uma referência para um SoundManager
na criação do player, o som do vídeo também será tocado em sincronia com a imagem,
possivelmente com informação posicional se for passado também um emissor. O formato
suportado para os vídeos é o Ogg Theora, que é um formato aberto mantido pela fundação
Xiph.org. Já o áudio poderá estar em qualquer formato suportado pela biblioteca de som
utilizada pelo SoundManager.
2.3 Detalhes da Implementação 36
2.3.3 Questões multiplataforma
Todo o arcabouço teve em mente a importância do paradigma de múltiplas plataformas.
Apesar de que para jogos comerciais a plataforma Windows, atualmente, é amplamente a mais
utilizada, recentes números mostram que o suporte para desenvolvedores independentes é maior
entre usuários de Linux e MacOS. No Humble Indie Bundle[15], uma promoção de diversos jo-
gos independentes com uma abordagem que deixava o usuário pagar o quanto quisesse pelo
pacote inteiro, apesar de mais da metade das compras terem sido feitas por usuários Windows,
o preço médio pago por eles foi o mais baixo (US$8,05 contra US$10,17 de usuários MacOS
e US$14,44 de usuários Linux). Na segunda edição desta promoção, no ano de 2010, os nú-
meros são mais expressivos: US$6,68 , US$9,27 e US$13,78 foram os preços médios pagos
por usuários Windows, MacOS e Linux, respectivamente. Além disso, há o fator "oceano azul"
agregado: como há poucos jogos em plataformas Apple e Linux, é mais fácil destacar-se nesse
ambiente.
No Dystopia, todo o código foi planejado para utilizar apenas comandos independentes de
plataforma. Quando isso não era possível, flags eram criadas no código para utilizar as versões
específicas dos comandos. Foi feita uma rápida conversão para ambiente Linux para comprovar
que esta abordagem foi feita de maneira correta e constatou-se que havia apenas dois pontos em
que alguma intervenção era necessária:
• Temporizador: o comando para se aguardar um tempo determinado sem nenhuma ação
em plataforma Win32 é o Sleep(). No Linux, há o usleep(), que recebe microssegundos
(µ).
• Entrada: a biblioteca padrão de entrada do arcabouço é a OIS (Object Oriented Input
System). Para a configuração do mouse nesta biblioteca deve-se passar dois parâmetros
relacionados a capturar a entrada do mouse e mostrar o cursor ou não quando este está
sobre a janela da aplicação. Essas constantes são diferentes entre Windows e Linux mas,
após trocar esses parâmetros, o funcionamento foi idêntico.
Sendo assim, constatou-se que o código do arcabouço era perfeitamente compatível entre os
dois sistemas operacionais, sendo a maior parte do trabalho de porte a configuração do ambi-
ente. Não foram realizados testes paras plataformas Apple por falta de equipamentos mas, por
ser o MacOS uma variação do Unix como o Linux, acreditamos que não haja grande diferença
no processo.
37
3 O jogo: SumoCheckers
"HAI!”
Red Sumotori #9 (SumoCheckers)
Neste capítulo é apresentado o processo de desenvolvimento do jogo, desde a criação do
primeiro protótipo até a versão atual, em 3D.
3.1 Introdução 38
3.1 Introdução
Tendo a estrutura básica do arcabouço definida, o próximo passo consistia em colocá-lo
à prova num ambiente de desenvolvimento real. Assim, foi decidido criar um jogo que fosse
simples o suficiente para uma equipe pequena (no caso, duas pessoas), mas que envolvesse todos
os módulos do sistema. Foi feito então um rascunho dos requisitos: o jogo deveria ter gráficos,
sons, suportar mais de um jogador (localmente e via rede) e ter entradas utilizando mouse,
teclado e, opcionalmente, joysticks. Outro objetivo importante foi o jogo ser um ambiente onde
pudessem ser testados diferentes tipos de inteligência artificial.
Atendendo a esses requisitos, foi criado o SumoCheckers, um jogo de estratégia por turnos
com batalhas em tempo real. Basicamente, peças de damas seriam espalhadas num tabuleiro
e, quando uma peça fosse capturar a outra, haveria uma batalha semelhante ao sumô, sendo
perdedora a primeira peça que saísse de um ringue circular. É importante notar que o movi-
mento na arena da batalha é feito controlando-se a aceleração do personagem, e não velocidade
diretamente, o que torna esta mecânica bem mais interessante e desafiadora – mas, ao mesmo
tempo, foi o fator responsável por alguns problemas técnicos, vistos na seção 3.2.3.
A relação entre jogo e framework foi simbiótica: grande parte do desenvolvimento foi feita
em paralelo, pesando sempre a prioridade de implementação dos módulos do arcabouço com as
necessidades do jogo, e usando os limitadores da tecnologia para moldar o gameplay.
Neste capítulo, abordaremos as diferentes versões do jogo, as experiências em redes e inte-
ligência artificial feitas e a evolução das regras de acordo com as limitações da tecnologia.
3.2 Protótipo Inicial (2D)
A primeira versão do jogo foi implementada com gráficos 2D simples, consistindo apenas
em quadrados e círculos coloridos representativos do tabuleiro, das peças e do ringue. Como
os objetivos principais desta iteração eram testar a estrutura básica do framework, os módulos
de rede e entrada, além de realizar experiências de IA, pouca atenção foi dada à estética neste
momento. Logo, o jogo ainda não contava com gráficos além do essencial para visualização dos
elementos, e não havia qualquer forma de música ou efeitos sonoros, tanto em conteúdo como
em implementação.
É importante salientar uma vantagem paralela (porém bastante relevante) de se criar um
protótipo inicial desprovido de estética: a experiência do jogador é fortemente influenciada
pelos fatores audiovisuais. Sendo o protótipo desprovido disso, o fator mais aparente na expe-
3.2 Protótipo Inicial (2D) 39
Figura 2: Aparência do primeiro protótipo do SumoCheckers
riência é o núcleo do gameplay–exatamente o que se quer pôr à prova nos estágios iniciais do
desenvolvimento, juntamente com a tecnologia proposta. É isso que, no fim das contas, deter-
mina a rejogabilidade e a qualidade do jogo, no quesito diversão; ao longo da história muitos
jogos que tinham gráficos e sons acima da média de sua época não tiveram a longevidade de
títulos clássicos como da era de videogames 8-bits, que são jogados até hoje.
Sendo assim, todo jogo deve passar por um período de prototipação onde cada ação mais
básica deve ser testada e iterada diversas vezes. É a qualidade dessas ações que determinam se
o jogador irá passar mais ou menos tempo com aquele título. Um exemplo clássico é o do jogo
Super Mario 64, um carro-chefe da plataforma Nintendo 64, onde a equipe de desenvolvedores
passou meses refinando uma pequena demo onde o personagem principal apenas podia correr
e pular. Apenas quando estas ações estavam bem sintonizadas deu-se início a produção de
diferentes níveis e conteúdo.
Uma falha muito comum (não só em desenvolvedores iniciantes, mas também em gran-
des títulos) é tentar corrigir as falhas encontradas nos testes de ações básicas adicionando mais
funcionalidades ao jogo. Diversos jogos com dezenas de mecânicas no seu gameplay não con-
quistaram sucesso critico e comercial porque as ações mais básicas (algo tão primário como
o feel de correr e pular com seu personagem) não eram agradáveis, ou porque o conjunto das
mesmas não era bem unificado.
No que se diz respeito à implementação, o protótipo do SumoCheckers foi projetado com
um paradigma cliente-servidor completo, ou seja, sempre haveria um servidor e dois clientes
sendo executados em um dado momento, sendo que um dos clientes poderia um jogador real
ou um oponente virtual controlado por IA. Toda a comunicação entre clientes e servidor seria
3.2 Protótipo Inicial (2D) 40
realizada por meio da interface de rede, mesmo que todos estivessem sendo executados na
mesma máquina. Tal estratégia foi utilizada para minimizar a redundância de código e ajudar a
garantir o isolamento entre a interface com o jogador e a lógica central (regras) do jogo.
Como no momento do desenvolvimento do protótipo 2D ainda não havia o módulo da
Aplicação Orientada a Objetos (seção 2.2.2.3), o controle básico de inicialização e execução foi
feito com uma função “main” padrão da linguagem. Porém, a estrutura de Máquina de Estados
(seção 2.2.2.2) já havia sido construída e foi utilizada nesta versão: havia um estado para o
tabuleiro e outro para a arena para o servidor, o cliente humano e o cliente com IA (veja a seção
3.2.2), num total de 6 estados. O estado inicial de todos os jogadores e do servidor é o do
tabuleiro. Ao iniciar uma batalha, o estado da arena é empilhado sobre o do tabuleiro, e ao fim
desta, desempilhado para que a execução retorne, sem perda de informações.
3.2.1 Bibliotecas externas utilizadas
Para este primeiro protótipo, eram necessárias bibliotecas que tratassem da parte gráfica,
entrada (teclado e mouse) e rede. As bibliotecas utilizadas para entrada e rede foram respec-
tivamente a OIS[10] e a RakNet[9], conforme descrito na seção 2.3.1, por intermédio de suas
interfaces respectivas (InputManager e NetworkInterface). Por outro lado, como a arquitetura
do framework não comporta wrappers para bibliotecas gráficas, tal escolha foi feita especifica-
mente para esta versão do jogo.
Optou-se então pelo uso da biblioteca SDL[16], uma das mais populares disponíveis para
uso livre e destinada primariamente à confecção de aplicativos interativos com gráficos 2D. Por
mais que esta biblioteca provenha funcionalidades adicionais, como gerenciamento de áudio,
entrada e threads, foram utilizadas apenas as funcionalidades gráficas: criação e gerenciamento
de janelas, e desenho bidimensional. Uma documentação sobre esta biblioteca, principalmente
o subsistema gráfico, pode ser encontrada em [17]. O fator determinante para esta escolha foi a
experiência prévia com o SDL e a facilidade de seu uso para gráficos bidimensionais.
A unidade básica de desenho na SDL é uma superfície, que é representação de uma imagem
matricial em memória. A tela é representada por uma superfície, e a operação básica de desenho
é a cópia de parte de uma superfície para outra. Desta forma, foram geradas (analiticamente)
superfícies que representavam o tabuleiro, o ringue e as peças. A cada quadro do jogo, estas
superfícies eram redesenhadas na tela em suas posições apropriadas.
Posteriormente foram desenvolvidos gráficos um pouco melhorados, com imagens pré-
renderizadas para o tabuleiro, ringue e peças no lugar das figuras geométricas simples anteriores
3.2 Protótipo Inicial (2D) 41
Figura 3: Segunda versão dos gráficos do SumoCheckers
(figura 3). Apesar de não haver absolutamente nenhuma mudança na mecânica do jogo, a me-
lhora estética causa uma mudança clara na reação dos jogadores e torna a experiência geral bem
mais agradável, mesmo para um protótipo. Enquanto para alguém acostumado com desenvol-
vimento de jogos abstrair da parte gráfica é algo corriqueiro, para jogadores e interessados nem
sempre isso ocorre. Sendo assim, com essa pequena mudança foi possível conseguir pessoas de
perfis mais diferenciados para testar as mesmas mecânicas propostas anteriormente.
3.2.2 Experimentos com Inteligência Artificial
O SumoCheckers é composto de duas mecânicas bastante distintas: o tabuleiro, jogado por
turnos e com movimentação lenta; e a arena, jogado em tempo real com movimentos rápidos.
Isto faz com que estratégias bastante distintas sejam necessárias para abordar o problema da
criação de um oponente virtual para o jogo.
Por um lado, o problema da IA para um jogo de tabuleiro tradicional como damas é bastante
conhecido e estudado; o algoritmo Min-Max ou Minimax[18] é amplamente reconhecido como
uma opção muito boa para gerar oponentes virtuais desafiadores para este jogo, sendo utilizado
para implementar a IA no tabuleiro (uma ótima referência sobre este tipo de algoritmo pode ser
encontrada em [19]). Portanto, o foco da pesquisa em IA desenvolvida no momento da criação
deste protótipo foi o controle de oponentes em tempo real.
Como o Laboratório de Inteligência Computacional do NCE-UFRJ, onde foi desenvolvido
este trabalho, tem como foco a pesquisa em Lógica Nebulosa, Algoritmos Genéticos e Redes
Neurais Artificiais, foi considerada a utilização de alguma destas técnicas para desenvolvimento
do oponente virtual para a arena. Decidiu-se então, inicialmente, pela utilização de Redes
Neurais Artificiais (RNAs), que seriam de implementação mais simples e rápida.
3.2 Protótipo Inicial (2D) 42
Isentando-nos de uma explicação extensa a respeito do funcionamento das RNAs (para um
livro dedicado a este assunto, veja [20]), podemos nos limitar a dizer, de maneira simplificada,
que uma RNA do tipo MLP (Multi-Layer Perceptron, como a que foi utilizada neste caso) é
um aproximador de funções contínuas definidas de Rn → Rm, treinado a partir de um conjunto
de pares de entrada e saída conhecidos. Este tipo de rede é composta de várias camadas de
neurônios artificiais que processam os dados de entrada e geram uma saída correspondente.
Para gerar o conjunto de amostras conhecidas, uma sessão de aproximadamente 10 minutos
de jogo entre dois jogadores reais foi gravada, com cada comando de movimentação realizado
por cada jogador (inclusive a ausência de comandos) em cada estado da arena representando
uma amostra do treinamento.
Foram utilizadas então duas estratégias para organizar estas amostras para o treinamento.
Na primeira, uma amostra de entrada era formada pela posição e velocidade atual, em X e
Y, de cada jogador, totalizando 8 dimensões. As saídas eram apenas a direção, em X e em
Y, para onde o jogador deveria se mover. Na segunda, foi aproveitado o fato de que a arena é
circularmente simétrica: as posições dos jogadores foram convertidas para coordenadas polares,
e foi considerado que um dos jogadores sempre estava no ângulo zero, ou seja, a coordenada do
ângulo de um jogador é subtraída do outro, e elimina-se uma dimensão de entrada, chegando a
7 dimensões.
A arquitetura de rede MLP foi definida como tendo duas camadas de neurônios: uma ca-
mada de saída e uma camada escondida. A camada de saída possui sempre um neurônio artifi-
cial para cada dimensão de saída, e a camada escondida foi definida como tendo 12 neurônios
nas duas abordagens utilizadas.
Após o treinamento de uma RNA para cada uma das duas abordagens, foi observado o com-
portamento de cada uma quando posta à prova frente a um jogador real. Para relativa surpresa,
a segunda abordagem, que esperava-se ser mais adequada pela compactação de amostras aná-
logas, terminou por mostrar-se evidentemente menos eficaz que a primeira quando executada
durante o jogo. Por sua vez, a abordagem das entradas diretas, apesar de se sair melhor que
a outra, ainda fraquejava em algumas situações, aparentando estar “confusa” e “indecisa”, e
acabando por ficar parada no mesmo lugar e abrindo espaço para ser atacada.
Uma provável explicação para este cenário é que RNAs, ao contrário de seres humanos,
são inerentemente determinísticas e baseadas apenas nas entradas que lhes são apresentadas.
Os jogadores que geraram o conjunto de amostras de treinamento provavelmente reagiram de
maneiras diferentes em situações muito parecidas, seja por motivos externos à situação do jogo
em si ou porque humanos são (aparentemente) não-determinísticos. Como o treinamento da
3.2 Protótipo Inicial (2D) 43
MLP se dá a partir da minimização de um erro médio quadrático, a tendência no caso de amos-
tras conflitantes é que a saída se equilibre na média entre as duas respostas, que em muitos
destes casos (onde há uma escolha entre se esquivar para a direita e para a esquerda, por exem-
plo) equivale ao oponente permanacer parado.
Esta análise também explicaria por que a segunda abordagem à criação das amostras foi
pior: com a “compactação” das amostras, uma quantidade ainda maior de situações conflitantes
passou a existir no conjunto de treinamento, criando menos regiões de entrada onde a rede
retorna uma saída “decidida”.
3.2.3 Experimentos com jogo em rede e Internet
Inicialmente, para o prototipo 2D, desenvolveu-se um sistema de controle via rede total-
mente bloqueante: o usuário entra com um comando, este é enviado para o servidor para vali-
dação, e só então os clientes (inclusive o que enviou o comando) recebem a resposta e a aplicam
em seus estado locais do jogo. Apesar deste esquema funcionar bem o suficiente para o tabu-
leiro, e mesmo para a arena em jogos em LAN sem tráfego paralelo, ele é totalmente inviável
para a arena em jogos via Internet devido à alta latência envolvida neste tipo de comunicação,
que é da ordem de centenas de milissegundos em grande parte das vezes.
Sendo assim, uma solução que, ao menos aparentemente, removesse esta impressão de
atraso percebida pelo jogador se fez necessária. A estratégia mais óbvia neste caso é simples-
mente deixar que o jogador tenha autoridade sobre a validade de seu movimento, retirando do
servidor a tarefa de validar todos os comandos antes que o jogador veja os resultados. Além
disso, a validação ainda pode ocorrer a posteriori, enviando uma mensagem que cancela o co-
mando anterior e reverte o estado do jogo para o que o servidor considera “válido”.
Porém, isto ainda apresenta dois problemas. O primeiro, que surge em todo jogo com movi-
mentação em tempo real, é que o outro jogador ainda receberá a informação do movimento com
algum atraso, causando uma discrepância na posição e velocidade dos jogadores nos mundos
percebidos por cada um. Esta questão pode ser contornada, porém, supondo que nenhum outro
comando foi enviado durante o atraso da transmissão, estimando este atraso, e extrapolando a
posição e velocidade do outro jogador baseando-se nas informações recebidas na comunicação
e no tempo estimado para sua recepção. Caso tenha havido de fato algum outro comando envi-
ado neste meio tempo, basta que seja sempre enviado o estado completo do mundo percebido
pelo servidor para que os erros devidos a estas estimativas não se acumulem muito. Esta técnica
é chamada de compensação de lag (ou lag compensation[21]).
3.3 Versão 3D 44
O segundo problema, específico à maneira como os objetos se movimentam e reagem no
SumoCheckers, ocorre porque o movimento de um jogador pode influenciar diretamente no
outro, no momento em que ocorre uma colisão. Um atraso de algumas dezenas de milissegundos
já é suficiente para causar uma grande discrepância no estado do mundo percebido por cada
jogador quando uma colisão ocorre neste tempo, fazendo com que o servidor tenha que tomar
decisões difíceis. Por exemplo, se na visão de um jogador ele conseguiu esbarrar, por pouco,
no outro, mas para o outro jogador, este realizou um movimento que o fez esquivar-se a tempo,
qual situação o servidor deve arbitrar como válida se ambas são válidas de acordo com o estado
do universo no momento em que foram processadas?
Uma rápida pesquisa mostrou que existem muito poucos tipos de jogos com este tipo de
comportamento, com controle de movimentação acelerada e onde um jogador afeta direta e for-
temente a movimentaçao de outro(s), com a opção de jogos multiplayer online. Uma exceção
são os jogos de corrida, onde pode-se perceber que este problema de fato existe e não con-
segue ser contornado satisfatoriamente: colisões do carro do jogador com o cenário em geral
se comportam bem, mas colisões com outros jogadores online muitas vezes causam resultados
estranhos e uma sensação inverossimel para o jogador. Ainda assim, este problema é um pouco
menor neste tipo de jogo devido à frequência das colisões com outros jogadores ser bem menor
que no caso do SumoCheckers (onde estas colisões são de fato a base do próprio gameplay), e
pelo controle da movimentação dos carros ser mais “pesado”, tendo assim uma trajetória difícil
de mudar repentinamente.
Sendo assim, os esforços para a implementação de um sistema satisfatório de diminuição
dos efeitos percebidos pela latência da comunicação via Internet foram abandonados por terem
se mostrado um problema grande demais para o jogo em questão, ainda mais após perceber a
dificuldade em encontrar alguma evidência, bibliográfica ou visual, de que esta questão foi sa-
tisfatoriamente resolvida para situações suficientemente similares. Soluções para este problema
são abordadas na seção 3.3.4.
3.3 Versão 3D
Após verificarmos que o SumoCheckers era de fato um projeto factível, tanto nos quesitos
de quantidade de trabalho quanto em relação ao funcionamento das mecânicas (mesmo que
fossem necessárias algumas mudanças), partimos para o próximo passo, tanto do jogo quanto
do arcabouço. Nesta fase, foram criados os módulos de Áudio e CVGA (seções 2.2.1.3 e 2.2.2.4,
respectivamente), além da adaptação do Dystopia para trabalhar com a engine gráfica OGRE3D
3.3 Versão 3D 45
(seção 2.3.2). Devido aos problemas relacionados à compensação de lag vistos na seção 3.2.3,
a implementação de jogo por rede foi adiada, sendo esta versão, atualmente, jogável apenas por
dois jogadores na mesma máquina.
3.3.1 Conteúdo
A diferença primordial do protótipo bidimensional para esta versão é que era preciso co-
meçar a se definir uma direção geral no estilo visual do jogo. Mais que um teste de tecnologia
e de gameplay, o projeto agora deveria tomar direções que apontassem mais para uma versão
finalizada, para evitar retrabalho futuro também na parte de conteúdo. Assim, cada elemento
deveria ser considerado ou como algo a ser feito de maneira simples e rápida, que seria comple-
tamente substituído mais adiante, ou algo que pudesse ser iterado posteriormente para se tornar
parte integrante da versão final.
Outra diferença é que os gráficos da segunda versão do protótipo puderam ser criados de
maneira rápida. Na criação de conteúdo (modelos) 3d, os passos a serem tomados são bem mais
complexos, especialmente em relação a personagens animados:
• Arte conceitual: um esboço do personagem que servirá de base para sua recriação em 3d;
• Modelagem: é criada uma escultura tridimensional do personagem, observando sempre
as limitações impostas pela plataforma e tecnologia sendo utilizadas;
• Preparação para texturização: cria-se um mapa bidimensional, que projeta o modelo 3d
em um plano, para que sua textura seja pintada (UVW Map);
• Texturização: pinta-se por cima da guia do UVW Map, para que essa textura seja proje-
tada no modelo;
• Preparação para animação: processo chamado de rigging, onde um esqueleto é construído
e diferentes partes do modelo são associadas a cada osso [22]. Movendo-se o esqueleto,
move-se estas partes, facilitando o processo de animação;
• Animação: são criados keyframes, ou seja, quadros com poses fixas que serão interpola-
das pelo programa de criação de conteúdo para gerar os movimentos;
• Exportação: o modelo texturizado é exportado para o formato padrão da engine sendo
usada (neste caso, da OGRE3D).
3.3 Versão 3D 46
Figura 4: Aparência da versão 3D do SumoCheckers
3.3 Versão 3D 47
Alguns desses processos podem ser paralelizados (por exemplo, animação e texturização) em
equipes que contem com mais de um artista. Em objetos estáticos, apenas os quatro primeiros
passos são feitos, além da exportação. Como os modelos são artefatos lógicos a serem guarda-
dos em memória e que, para sua exibição, envolvem cálculos matemáticos intensos, dois fatores
devem ser observados com cuidado: a contagem de polígonos e a resolução das texturas. Deve-
se sempre pesar quantos modelos serão exibidos simultaneamente, tanto no cenário como em
personagens/adereços, com a sua importância visual, tendo como guia a plataforma; enquanto é
aceitável que para um jogo que vá rodar em uma placa de vídeo de última geração e ter poucos
personagens na tela que um modelo tenha de 15 a 20 mil polígonos e texturas de 1024x1024
pixels, tentar fazer o mesmo em um netbook ou smartphone seria completamente inviável.
A criação das cenas segue um fluxo semelhante ao dos modelos individuais. No entanto,
ela é exportada como um todo para o padrão DotScene[23], criado pela comunidade para a
OGRE3D, que consiste basicamente em um arquivo XML que descreve as posições e rota-
ções de diversos SceneNodes relativos a cada objeto da cena. Esta é carregada através de uma
pequena extensão chamada DotSceneLoader[24]. Enquanto pode-se criar a cena inteira num
programa gráfico e exportar todos os objetos, além do arquivo .scene de uma so vez, é prefe-
rível exportar os modelos separadamente e criar a cena em um editor, de forma que modelos
idênticos não sejam duplicados sem necessidade.
Para economizar processamento sem perder qualidade gráfica, algumas técnicas são utili-
zadas na criação dos cenários, sendo vistas na seção 3.3.2.
Finalmente, o diferencial final era a presença de sons. Com essa versão, foi desenvolvido
o sistema de áudio do Dystopia, sendo possível a adição de diferentes músicas para a fase de
tabuleiro (calma) e de batalha (agitada), além de efeitos sonoros para os personagens, não so
para ambientação, mas também para feedback, indicando quando movimentos eram aceitos,
colisão entre as peças e a derrota de um oponente.
3.3.2 Efeitos e melhorias visuais
Para garantir a melhor qualidade visual possível, diversos truques podem ser utilizados,
como criar adereços de cenário que consistam apenas de um plano tridimensional simples, com
uma textura pintada por cima, para dar a ilusão de um ambiente extenso (técnicas similares são
utilizadas no cinema há décadas, os chamados "matte paintings" [25]). Outra possibilidade é o
uso de técnicas de LOD, ou Level of Detail[26], onde diversas versões do mesmo modelo com
uma quantidade cada vez menor de polígonos são criadas e, quanto mais longe se está do objeto,
versões cada vez menos detalhadas são desenhadas na tela.
3.3 Versão 3D 48
Figura 5: Técnicas utilizadas para criação do cenário do jogo
Algumas vezes, efeitos simples podem fazer uma grande diferença na ambientação do jo-
gador. Além disso, há certas técnicas que já são um padrão esperado para jogos nos dias de
hoje. Além da definição de feedback visual para certas situações (como diferentes texturas para
quando as peças se transformassem em damas), foram realizadas duas melhorias na versão em
3d do jogo: a adição de grama no cenário e de um shader de normal mapping nos personagens.
A adição de grama é feita através de centenas de pequenos planos com uma textura de
grama que, quando sobrepostos e colocados de maneira aleatória no cenário, criam a ilusão de
folhas individuais (mas com um certo volume). Em placas gráficas atuais, o pipeline de rende-
rização trabalha melhor com poucos batches (chamadas de renderização) compostos de muitos
polígonos, comparado a muitos batches de poucos polígonos. Sendo assim, quando há diver-
sos modelos que usam o mesmo material (que equivale a um render state do driver de vídeo),
pode-se acelerar bastante o processamento agrupando-os em um só batch. Na OGRE3D, existe
uma solução pronta chamada StaticGeometry, implementando exatamente este mecanismo; esta
solução foi então utilizada para renderizar a grama do cenário do jogo.
A figura 5 mostra como são organizados na cena os componentes que definem o cenário
do jogo, em três camadas: em (a), um plano circular com aplicação de uma textura de floresta;
em (b), algumas instâncias de modelos de troncos de árvores e pedras, que completam a ilusão
de profundidade da imagem plana em (a); em (c), dois planos circulares com aplicação de uma
textura de arbustos, levemente rotacionados um em relação ao outro, fechando o volume do
ambiente.
Shaders são pequenos programas que rodam diretamente na placa gráfica. Com eles, é
3.3 Versão 3D 49
possível gerar de maneira mais eficiente diversos efeitos de cores e iluminação, sendo estes
partes integrantes de qualquer jogo 3d moderno. Antigamente, o sombreamento de modelos
3d era calculado apenas com base nos vértices ou nos triângulos de sua estrutura. Atualmente,
como ainda é muito mais custoso calcular transformações em malhas complexas de polígonos, a
maior parte dos detalhes mais finos de personagens e cenários virtuais é feita através de técnicas
que utilizam mapas de textura como base, já que as placas trabalham bem mais rápido com as
informações contidas neles.
Uma dessas técnicas é o Normal Mapping[27], que consiste em calcular a direção em que
a luz refletiria num objeto de acordo com um mapa de textura bidimensional, gerado a partir
de um modelo de alta quantidade de polígonos. Basicamente, cria-se o modelo 3d de base (que
será utilizado dentro da aplicação em tempo real), com uma contagem baixa de polígonos, e
outro com uma alta contagem de polígonos e muito detalhado, com base no anterior. Através
das informações do UVW Map, uma projeção é feita de um em outro, criando, assim, o normal
map. Essa textura extra é utilizada por um shader, que a mescla com as informações do mapa de
texturas de cor e da iluminação da cena, criando assim a ilusão de um modelo muito detalhado
e com muitos polígonos, mas que na verdade é simples e renderizado muito mais rapidamente.
O pipeline de criação gráfica varia, sendo algumas vezes o modelo em alta definição criado
primeiro, e o em baixa a partir deste, utilizando-se uma técnica chamada "retopology".
A figura 6 exemplifica os efeitos da técnica de Normal Mapping aplicada aos personagens
do SumoCheckers. Fica evidente na comparação não somente o aumento do detalhe perce-
bido no modelo, mas também o maior realismo alcançado pelo detalhamento da iluminação,
evidenciando nuances mais humanos e diminuindo a impressão “plástica” do modelo original.
3.3.3 Adaptação da lógica de jogo
Por mais que a mecânica de gameplay fosse inerentemente a mesma entre o protótipo 2D e a
versão 3D do jogo, foi decidido começar a implementação do zero, aproveitando as experiências
anteriores para desenvolver uma arquitetura mais organizada e aproveitando novos componentes
do framework que foram desenvolvidos ao longo do tempo.
O paradigma cliente-servidor completo, apesar de robusto e adequado para um jogo mais
complicado, acabou por adicionar complexidade desnecessária no caso do SumoCheckers, por-
que o controle do fluxo das informações estava espalhado por vários lugares diferentes (ora no
cliente, ora no servidor). Além disso, vários trechos do código muito parecidos, porém não o
suficiente para serem encapsulados em uma só, acabavam duplicados nos dois lados, sem con-
tar a quantidade de código necessária para repassar os comandos do jogo e o estado do mundo
3.3 Versão 3D 50
Figura 6: Comparação exemplificando a utilização da técnica de Normal Mapping
3.3 Versão 3D 51
entre servidor e clientes. Por isso, esta idéia foi abandonada no desenvolvimento da versão 3D
atual em função de um fluxo de execução autocontido.
A estrutura da máquina de estados, com um estado para o tabuleiro e outro pra a arena,
por outro lado, se mostrou eficaz, sendo repetida para a elaboração desta nova versão. Outro
trecho de funcionalidade que seguiu inalterado foi a maneira de organizar o tabuleiro, como
uma simples matriz de inteiros 8x8, e a função de validação de movimento (que define as regras
do jogo).
Por fim, uma mudança importante na arquitetura foi a utilização da estrutura de Aplicação
Orientada a Objetos OGRE, que auxiliou bastante na organização do código e resultou quase
que na total ausência de código de inicialização da biblioteca gráfica, sendo apenas necessário
inicializar os elementos das cenas (cenários e personagens).
Para integrar de maneira simples e direta o espaço contínuo 3D da cena com o espaço dis-
creto 2D das peças no tabuleiro, foi criada na hierarquia de transformações da cena do tabuleiro
um nó que colocava o ponto (0, 0, 0) no meio da superfície da posição (0, 0) do tabuleiro, e fazia
com que uma unidade 3D equivalesse à distância lateral entre posições do mesmo. Desta forma,
uma posição (x, y) do tabuleiro poderia ser utilizada diretamente como (x, y, 0) para posicionar
uma peça do tabuleiro na cena 3D, tornando a implementação da lógica de jogo mais intuitiva.
3.3.4 Playtests e mudanças no gameplay
Tendo um protótipo em 3D com o núcleo das mecânicas implementado, passou-se a uma
nova fase de testes. Como já abordado na seção 3.2, é essencial que as ações mais básicas
do gameplay sejam divertidas o bastante para prender a atenção do jogador. Além disso, o
feedback audiovisual deve ser considerado a cada nova versão, já que alterações nos gráficos e
sons podem alterar profundamente a experiência.
Nesta transição, fomos de um rascunho em 2D, onde o que o jogador experiencia é a colisão
entre dois pequenos círculos (tanto na versão com cores sólidas quanto na com desenhos), a
uma representação bem mais realista, com volume e personagens que, apesar de não serem
tão fotorrealistas, representam claramente um ser humano com certas características visuais.
Devido a essas mudanças, algo inesperado surgiu: o movimento dos personagens não era mais
tão bem aceito quanto nas suas representações anteriores.
Enquanto círculos com desenhos em cima são facilmente aceitos tendo seus movimentos
similares a objetos que deslizam, uma representação tridimensional de um lutador de sumô
carrega mais significado ao jogador; a noção de volume e a representação de características
3.3 Versão 3D 52
físicas como força muscular e peso corporal fazem o movimento anterior se tornar alheio ao
que se espera. Aliado a isso, este mesmo tipo de movimento era um dos maiores responsáveis
pelos problemas na implementação de compensação de lag vistos na seção 3.2.3. Assim, vimos
a necessidade de adaptação da mecânica primordial do jogo: as lutas em tempo real.
Atualmente, estudamos diversas opções para manter o mesmo tipo de gameplay, mas que
resolvam estes problemas. Uma das abordagens a ser testada é a de os movimentos onde há
contato entre os dois personagens serem controlados de maneira indireta, como por exemplo,
apenas definir uma direção e "atacar" nesta, não havendo a possibilidade de alteração da rota.
Com isso, a movimentação das peças poderia ser adicionada de mais "peso", se tornando mais
parecida com a humana e, ao mesmo tempo, poderíamos utilizar técnicas amplamente testadas
de compensação de lag–neste caso, definir apenas se dado um vetor de direção haverá ou não
uma colisão com outro objeto. [21]
53
4 Conclusões e trabalhos futuros
“Thank you Mario! But our princess is in another castle!”
Toad (Super Mario Bros.)
Neste capítulo são apresentadas as conclusões e alguns trabalhos ainda a serem realizados.
4 Conclusões e trabalhos futuros 54
Criar um jogo eletrônico desde suas bases é claramente um trabalho extenso e intrincado.
Enquanto utilizando uma engine pronta o trabalho de implementação é acelerado significativa-
mente (já que o necessário é apenas a programação da lógica do jogo e a criação de conteúdos
audio-visuais), criar um arcabouço funcional como o Dystopia só foi possível porque pudemos
utilizar diversas bibliotecas já existentes. Ao longo desses três anos de trabalho, passamos por
um período de surgimento de middlewares excelentes com licenças acessíveis ou até mesmo
gratuitas, projetos open-source já antigos atingindo sua maturidade e se estabelecendo como
padrões da indústria e, principalmente, de crescimento da comunidade de desenvolvimento de
jogos, com recursos para pesquisa e ajuda mútua amplamente presentes na Internet.
Das ferramentas e bibliotecas escolhidas, até então nenhuma apresentou problemas que
requeressem uma mudança. O mesmo pode-se dizer das decisões da arquitetura do arcabouço
que, apesar de ter sofrido modificações ao longo do tempo, nenhuma dessas afetou a direção
geral do projeto (sendo a maioria, inclusive, esperada). Desenvolver com foco na portabilidade
entre plataformas e na reusabilidade se provou, de fato, uma excelente escolha.
Trabalhos futuros incluem adicionar suporte completo à linguagem de scripting Lua[28] no
Dystopia. Com uma linguagem mais leve como a Lua tendo acesso a métodos do framework,
pessoas menos acostumadas com programação teriam mais facilidade de utilizá-lo para expe-
rimentos, já que não teriam que lidar com a maior complexidade do código em C++. Além
disso, haveria uma estrutura pronta para que a aplicação expusesse apenas partes da sua fun-
cionalidade para serem controladas por scripts. Posteriormente, seria feito o porte do código
da jogabilidade do SumoCheckers para esta linguagem, expondo todas as partes necessárias da
implementação, o que facilitaria e aceleraria o processo de iteração do gameplay.
Pesquisas em inteligência artificial e computacional também devem ser feitas, para criação
de um oponente virtual para o SumoCheckers que consiga não só se movimentar bem em tempo
real, mas também pesar a estratégia do tabuleiro de acordo com os resultados desta faceta do
jogo.
Finalmente, deve-se reconsiderar o paradigma cliente-servidor, quando novos testes de co-
municação via rede forem feitos, para validar as novas idéias de gameplay que possam contornar
o problema da compensação de lag, sem perder a essência das mecânicas desejadas.
Mais do que planejamento, é necessária a experiência direta com a implementação para
entender o quão complexa é a tarefa de se criar um jogo eletrônico do início ao fim. Com a
prática, entende-se melhor as limitações dos sistemas e, com isso, como trabalhar com elas
para atingir o melhor resultado possível. Dos requisitos de um único projeto, podem surgir
diversos outros, como por exemplo pesquisas em inteligência artificial (qual o melhor jeito de
4 Conclusões e trabalhos futuros 55
“ensinar” um oponente virtual a jogar um jogo conhecido em uma situação nova?), redes (como
podemos compensar os atrasos inerentes da comunicação via Internet?), computação gráfica
(qual a melhor maneira de se tratar informações para o pipeline de uma placa de vídeo para
renderização?); e até mesmo em áreas consideradas completamente alheias à computação (como
a representação de um objeto afeta a maneira que percebemos e interagimos com aquele mundo
virtual?). Com isso, podemos concluir que a pesquisa na área de jogos é não só gratificante, mas
também extremamente desafiadora, onde um simples jogo eletrônico pode requerer o esforço
de estudos profundos nas mais diversas áreas do conhecimento.
56
Referências
[1] DEMASI, P. Princípios fundamentais da programação de jogos eletrônicos através de umemulador por software. (Projeto Final de Curso). 2000.
[2] DEMASI, P. Estratégias adaptativas e evolutivas em tempo real para jogos eletrônicos. (Dis-sertação de Mestrado). 2003.
[3] HOMEPAGE da biblioteca OGRE3D. Disponível em: <http://www.ogre3d.org/>.
[4] WIKIPEDIA: Design Pattern. Disponível em: <http://en.wikipedia.org/wiki/Design_pattern_(computer_science)>.
[5] GREGORY, J. Game Engine Architecture. [S.l.]: A K Peters, 2009.
[6] HOMEPAGE da biblioteca OpenAL. Disponível em:<http://connect.creativelabs.com/openal/default.aspx>.
[7] HOMEPAGE da biblioteca FMOD. Disponível em: <http://www.fmod.org/>.
[8] HOMEPAGE da biblioteca IrrKlang. Disponível em:<http://www.ambiera.com/irrklang/>.
[9] HOMEPAGE da biblioteca RakNet. Disponível em: <http://www.jenkinssoftware.com/>.
[10] HOMEPAGE da biblioteca OIS. Disponível em: <http://sourceforge.net/projects/wgois/>.
[11] WIKIPEDIA: Wii Remote. Disponível em: <http://en.wikipedia.org/wiki/Wii_Remote>.
[12] WIKIPEDIA: Scene Graph. Disponível em: <http://en.wikipedia.org/wiki/Scene_graph>.
[13] JUNKER, G. Pro OGRE 3D Programming. [S.l.]: Apress, 2006.
[14] KERGER, F. OGRE 3D 1.7 Beginner’s Guide. [S.l.]: Packt Publishing, 2010.
[15] THE Humble Indie Bundle. Disponível em: <http://www.humblebundle.com/>.
[16] HOMEPAGE da biblioteca SDL. Disponível em: <http://www.libsdl.org/>.
[17] FERREIRA, B. B. A biblioteca multimídia sdl. 2007. Disponível em:<http://equipe.nce.ufrj.br/adriano/c/apostila/sdl/index.html>.
[18] WIKIPEDIA: Minimax. Disponível em: <http://en.wikipedia.org/wiki/Minimax>.
[19] Stuart Russell; Peter Norvig. Inteligência Artificial. 2a. ed. [S.l.]: Elsevier, 2004.
[20] Antonio de Pádua Braga; André Ponce de Leon F. de Carvalho; Teresa Bernarda Ludemir.Redes Neurais Artificiais - Teoria e Aplicações. 2a. ed. [S.l.]: LTC, 2007.
Referências 57
[21] BERNIER, Y. W. Latency compensating methods in client/server in-game pro-tocol design and optimization. Visitado em 21/02/2011. 2001. Disponível em:<http://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization>.
[22] WIKIPEDIA: Skeletal Animation. Disponível em:<http://en.wikipedia.org/wiki/Skeletal_animation>.
[23] OGRE Wiki: DotScene. Disponível em: <http://www.ogre3d.org/tikiwiki/DotScene>.
[24] OGRE Wiki: DotScene. Disponível em: <http://www.ogre3d.org/tikiwiki/New+DotScene+Loader>.
[25] WIKIPEDIA: Matte Painting. Disponível em: <http://en.wikipedia.org/wiki/Matte_painting>.
[26] WIKIPEDIA: Level of Detail. Disponível em: <http://en.wikipedia.org/wiki/Level_of_detail>.
[27] WIKIPEDIA: Normal Mapping. Disponível em: <http://en.wikipedia.org/wiki/Normal_mapping>.
[28] HOMEPAGE da biblioteca Lua. Disponível em: <http://www.lua.org/>.
58
ANEXO A -- Documentação das classes do Dystopia
Para utilizar as classes definidas no Dystopia, basta incluir o header “Dystopia.h” e definir
algumas constantes do pré-processador para indicar quais módulos deseja-se usar. Como o
Dystopia.h é apenas um meta-header que inclui todos os outros dependendo das constantes
definidas, basta investigá-lo para descobrir quais constantes devem ser utilizadas em cada caso.
Apenas a documentação para as classes principais consta neste anexo. As classes mais
simples são de utilização suficientemente intuitiva e/ou as explicações ao longo do texto cobrem
suficientemente sua funcionalidade.
A.1 Referência da Classe Dystopia::Application
#include <AppMain.h>
Diagrama de Hierarquia para Dystopia::Application:
Dystopia::Application
Dystopia::OgreApplication
Dystopia::KeyListener Dystopia::MouseListener Dystopia::JoystickListener
Métodos Públicos
• virtual void afterSetup ()
• virtual void frameUpdate (float timeDelta)
• virtual const char ∗ getWindowTitle ()
A.2 Referência da Classe Dystopia::InputManager 59
A.1.1 Descrição Detalhada
Provê um framework de aplicação interativa orientada a objetos. É implementado como um
Singleton.
A.1.2 Métodos
A.1.2.1 virtual void Dystopia::Application::afterSetup () [inline, virtual]
É chamado automaticamente após o término da execução do setup. Pode ser reimplemen-
tado pela aplicação, se necessário.
A.1.2.2 virtual void Dystopia::Application::frameUpdate (float timeDelta) [inline,virtual]
É chamado automaticamente a cada frame. Pode ser reimplementado pela aplicação, se
necessário.
A.1.2.3 virtual const char∗ Dystopia::Application::getWindowTitle () [inline,virtual]
Retorna o título da janela. Deve ser reimplementado pela aplicação.
A.2 Referência da Classe Dystopia::InputManager
#include <InputInterface.h>
Diagrama de Hierarquia para Dystopia::InputManager:
Dystopia::InputManager
Dystopia::OISInterface
A.2 Referência da Classe Dystopia::InputManager 60
Métodos Públicos
• void addKeyListener (KeyListener ∗kl)
• void addMouseListener (MouseListener ∗ml)
• void addJoystickListener (JoystickListener ∗jl)
• virtual void setWindowSize (int width, int height)
• virtual void update (void)=0
A.2.1 Descrição Detalhada
Interface para recebimento de eventos de entrada.
A.2.2 Métodos
A.2.2.1 void Dystopia::InputManager::addJoystickListener (JoystickListener ∗ jl)[inline]
Adiciona um objeto para receber eventos de joystick.
A.2.2.2 void Dystopia::InputManager::addKeyListener (KeyListener ∗ kl) [inline]
Adiciona um objeto para receber eventos de teclado.
A.2.2.3 void Dystopia::InputManager::addMouseListener (MouseListener ∗ ml)[inline]
Adiciona um objeto para receber eventos de mouse.
A.2.2.4 virtual void Dystopia::InputManager::setWindowSize (int width, int height)[inline, virtual]
Informa o tamanho da janela. Deve ser chamado na inicialização e sempre que o tamanho
da janela mudar.
A.2.2.5 virtual void Dystopia::InputManager::update (void) [pure virtual]
Atualiza o estado interno, recebendo informações dos dispositivos. Deve ser chamado pre-
ferencialmente a cada passo.
A.3 Referência da Classe Dystopia::NetworkInterface 61
A.3 Referência da Classe Dystopia::NetworkInterface
#include <NetworkInterface.h>
Diagrama de Hierarquia para Dystopia::NetworkInterface:
Dystopia::NetworkInterface
Dystopia::RakNetInterface
Métodos Públicos
• NetworkInterface (int role, int port)
• bool startup (char ∗serverAddress)
• bool startup (int maxClients)
• virtual void shutdown (void)=0
• int getNumberClients (void)
• virtual int update (void)=0
• virtual void send (unsigned char id, int len, char ∗data, int dest, bool broadcast=false,
bool timestamp=false)=0
• virtual bool pollPacket (NetworkPacket ∗∗p)=0
• virtual void freePacket (NetworkPacket ∗p)=0
A.3.1 Descrição Detalhada
Interface para comunicação cliente/servidor via LAN e Internet.
A.3.2 Construtores & Destrutores
A.3.2.1 Dystopia::NetworkInterface::NetworkInterface (int role, int port) [inline]
Constrói uma interface para conexão. role define se será um servidor (NETWORK_-
SERVER) ou cliente (NETWORK_CLIENT). port define a porta a ser aberta, no caso do servi-
dor, e a porta remota a ser conectada, no caso do cliente.
A.3 Referência da Classe Dystopia::NetworkInterface 62
A.3.3 Métodos
A.3.3.1 virtual void Dystopia::NetworkInterface::freePacket (NetworkPacket ∗ p)[pure virtual]
Libera os dados internos de um pacote já processado.
A.3.3.2 int Dystopia::NetworkInterface::getNumberClients (void)
Retorna a quantidade de clientes conetados, se for um servidor.
A.3.3.3 virtual bool Dystopia::NetworkInterface::pollPacket (NetworkPacket ∗∗ p)[pure virtual]
Verifica se há um pacote esperando na fila de pacotes. Em caso positivo, o copia para o
ponteiro passado.
A.3.3.4 virtual void Dystopia::NetworkInterface::send (unsigned char id, int len, char∗ data, int dest, bool broadcast = false, bool timestamp = false) [purevirtual]
Envia um pacote com o identificador id, tamanho len e dados contidos no endereço data.
Se for o servidor, dest indica o índice cliente destino, e broadcast define se será enviado para
todos.
A.3.3.5 virtual void Dystopia::NetworkInterface::shutdown (void) [pure virtual]
Fecha quaisquer conexões ativas e destrói a interface de rede criada.
A.3.3.6 bool Dystopia::NetworkInterface::startup (int maxClients)
Abre a porta definida para conexão de até maxClientes ao mesmo tempo, se for um servidor.
A.3.3.7 bool Dystopia::NetworkInterface::startup (char ∗ serverAddress)
Inicializa a conexão com o servidor remoto, se for um cliente.
A.4 Referência da Classe Dystopia::SoundManager 63
A.3.3.8 virtual int Dystopia::NetworkInterface::update (void) [pure virtual]
Atualiza o estado interno. Deve ser chamado preferencialmente a cada passo. Retorna um
código indicando que tudo está certo, ou alguma condição adversa aconteceu.
A.4 Referência da Classe Dystopia::SoundManager
#include <AudioInterface.h>
Diagrama de Hierarquia para Dystopia::SoundManager:
Dystopia::SoundManager
Dystopia::IrrKlangInterface
Métodos Públicos
• virtual int setupAudio ()=0
• virtual int loadSound (const char ∗file, std::string alias)=0
• virtual void unloadSound (std::string sound)=0
• virtual void unloadSound (int id)=0
• virtual void flushAllSounds ()
• int getSoundId (std::string label)
• void setWorldScale (Real scale)
• Real getWorldScale ()
• Vec3 getListenerPos ()
• Vec3 getListenerVel ()
• Vec3 getListenerUp ()
• Vec3 getListenerLook ()
• void setListenerPos (Vec3 pos)
• void setListenerVel (Vec3 vel)
• void setListenerUp (Vec3 up)
A.4 Referência da Classe Dystopia::SoundManager 64
• void setListenerLook (Vec3 look)
• void attachListenerToOgreCamera (Ogre::Camera ∗camera, bool autoUpdateVel=true)
• virtual SoundEmitter ∗ createEmitter ()=0
A.4.1 Descrição Detalhada
Interface para reprodução de som, posicional ou não. Tanto esta interface quando o Soun-
dEmitter possuem os métodos play, pause, stop, seek e getPos para tratar de sons sendo repro-
duzidos, sendo que os sons são não-posicionais se chamados no SoundManager (pag. 63), e
posicionais se chamados num emissor.
A.4.2 Métodos
A.4.2.1 void Dystopia::SoundManager::attachListenerToOgreCamera (Ogre::Camera∗ camera, bool autoUpdateVel = true) [inline]
"Liga" o ouvinte a uma câmera OGRE. Se autoUpdateVel for true, atualiza automatica-
mente também a velocidade.
A.4.2.2 virtual SoundEmitter∗ Dystopia::SoundManager::createEmitter () [purevirtual]
Cria um emissor de som.
A.4.2.3 virtual void Dystopia::SoundManager::flushAllSounds () [inline,virtual]
Descarrega todos os sons carregados atualmente.
A.4.2.4 Vec3 Dystopia::SoundManager::getListenerLook () [inline]
Retorna a direção "frente" do ouvinte.
A.4.2.5 Vec3 Dystopia::SoundManager::getListenerPos () [inline]
Retorna a posição do ouvinte.
A.4 Referência da Classe Dystopia::SoundManager 65
A.4.2.6 Vec3 Dystopia::SoundManager::getListenerUp () [inline]
Retorna a direção "para cima" do ouvinte.
A.4.2.7 Vec3 Dystopia::SoundManager::getListenerVel () [inline]
Retorna a velocidade do ouvinte.
A.4.2.8 int Dystopia::SoundManager::getSoundId (std::string label) [inline]
Retorna o identificador de um som a partir do nome.
A.4.2.9 Real Dystopia::SoundManager::getWorldScale () [inline]
Retorna a escala global do mundo (valor pelo qual todas as posições e velocidades são
multiplicados)
A.4.2.10 virtual int Dystopia::SoundManager::loadSound (const char ∗ file, std::stringalias) [pure virtual]
Cerrega um som do arquivo file e dá a ele o nome alias. Retorna seu identificador.
A.4.2.11 void Dystopia::SoundManager::setListenerLook (Vec3 look) [inline]
Define a direção "frente" do ouvinte.
A.4.2.12 void Dystopia::SoundManager::setListenerPos (Vec3 pos) [inline]
Define a posição do ouvinte.
A.4.2.13 void Dystopia::SoundManager::setListenerUp (Vec3 up) [inline]
Define a direção "para cima" do ouvinte.
A.4.2.14 void Dystopia::SoundManager::setListenerVel (Vec3 vel) [inline]
Define a velocidade do ouvinte.
A.4 Referência da Classe Dystopia::SoundManager 66
A.4.2.15 virtual int Dystopia::SoundManager::setupAudio () [pure virtual]
Inicializa o SoundManager (pag. 63).
A.4.2.16 void Dystopia::SoundManager::setWorldScale (Real scale) [inline]
Define a escala global do mundo (valor pelo qual todas as posições e velocidades são mul-
tiplicados)
A.4.2.17 virtual void Dystopia::SoundManager::unloadSound (int id) [purevirtual]
Descarrega o som de identificador id.
A.4.2.18 virtual void Dystopia::SoundManager::unloadSound (std::string sound)[pure virtual]
Descarrega o som de nome alias.