Ponto V!

Home Arquitetura Programação Criando jogos com a arquitetura MVC
Cristiano de Medeiros Ferreira
Criando jogos com a arquitetura MVCImprimir
Escrito por Cristiano de Medeiros Ferreira

A ideia do MVC é separar a lógica do jogo da forma como será apresentada, separando o projeto de forma a compartimentalizar o trabalho a ser feito.

MVC é muito comum em desenvolvimento de aplicações corporativas pela vantagem de ser escalável, já que as camadas são independentes entre si, e também em resolver o problema onde se reescrevia o código para cada nova tela, sendo comum colocar toda a lógica do negócio na tela atual, gerando, com o tempo, código ilegíveis que dificultavam a vida dos desenvolvedores.

“Mas você falou ‘sistemas’, porque então devo fazer um jogo usando MVC?”

Bom, todo jogo é um sistema, e um pouco de robustez não faz mal. O MVC torno o jogo mais fácil de escrever, entender E de atualizar no futuro. MVC ajuda a criar código mais claro, mais fácil de manter e menos código, gastando menos tempo no geral.

Bom, digamos que um loop típico de jogo é como esse aqui:

void main() {
   loadResources(); 
 
    while(!quit()) {
        readInput();
        updateGame();
        drawGame();
    }
}

Note que dentro desse método:

  • O jogo é inicializado, carregando todos os recursos que o jogo usará (texturas, modelos, sons, dados do nível, etc)
  • Tem um loop, cancelável pelo usuário, que lê os inputs do usuário (teclado/mouse/joystick/etc), atualiza o estado do jogo e desenha na tela.

Para projetos maiores ou multiplayer (MMOs principalmente) isso ficará impraticável. Pois temos num mesmo método a atualização de jogo e o desenho do mesmo. Provavelmente as entidades contém tanto a lógica do jogo, física, bem como os métodos para desenhar essa entidade... e isso é problemático pois mistura várias coisas em um mesmo código.

O designer gráfico pode ter problemas para entender o que o programador fez e vice-versa, deixando o código confuso, ou então ao invés de usar uma engine gráfica se prefere usar outra no meio do desenvolvimento... aí pode jogar todo o código fora ou gastar várias horas tentando entender o que é o que no código.

Proponho então separar o código em camadas independentes entre si.

Segue um modelo genérico de MVC para jogos:

Modelo genérico de MVC para jogos

(Ah, o modelo está incompleto, é mais para ter uma noção)

A vantagem do MVC para aplicações distribuídas é que ele é baseado em eventos, por exemplo:

  • Se jogador fez um comando válido, dispare um evento para o Controller.
  • Se veio um comando válido da View para o Controller, então dispare um evento apropriado para o modelo.
  • Se a propriedade da entidade foi alterada na camada física, então atualize o item lógico e (por conseguinte) o item visual.
  • Se o item lógico não existe mais (explodiu? Saiu do jogo?) então remova-o da camada física e da camada visual.
  • Inclusão e remoção de itensLógicos em arrays dispara eventos para registrar e desregistrar os respectivos objetos nas demais camadas quando aplicável.

Para disparar esses eventos do lado do modelo precisamos de métodos como esse abaixo:

public void setType(String type) {
    if (this.type != type)
        firePropertyChange( ItemsController.TYPE_PROPERTY, this.type, type );
        this.type = type;
    }
}

Isso, é claro, apenas para as propriedades que precisam ser replicadas.

Por exemplo, a quantidade de litros que tem em um tanque de gasolina pode ser necessário para a simulação mas dificilmente isso será necessário no lado do cliente (afinal, qual a diferença visual entre um carro com tanque cheio e outro com tanque vazio?). É preciso então estabelecer o que deve ou não ser replicado.

Camadas e suas características

Camada física

  • Camada responsável pela atualização da posição/rotação dos objetos e de suas colisões, ela é populada a partir da camada Controle quando o itemLógico é registrado.
  • Note que não é preciso implementar essa camada... engines físicas de terceiros podem muito bem ser utilizadas e facilmente substituídas, com praticamente nenhum retrabalho (ou o jogo pode não precisar de uma camada física).
  • A camada física não conhece/usa ninguém à direita dela, ou seja, é a base na qual o jogo se sustenta.
  • Normalmente é a camada que usa a CPU mais intensamente e a que exige maior precisão matemática, então escolha bem a engine física.

Camada Modelo (lógica)

  • Essa camada é responsável pela lógica do jogo propriamente dita, por exemplo, caso o jogador envie o comando “Atirar Míssil” essa camada pode verificar então se existem mísseis, se o alvo existe, se o ângulo de disparo está correto,etc e cria então um novo ItemLógico Míssil (e não se preocupa em como ele será desenhado ou simulado).
  • Toda a lógica do jogo deve ficar aqui, tudo o que as suas entidades podem ou não fazer.

Persistência em banco de dados: Uma possível sub-camada é a persistência em banco de dados (ou em arquivos).

É importante NÃO colocar a lógica do banco de dados junto da lógica do jogo, especificando em outro lugar como os objetos serão persistidos ao longo das sessões.

O mais usado em aplicações é o Hibernate, e ele aceita arquivos como esse abaixo:


  
  
      
          
              
          
        
            
        
        
      

É claro que isso é uma sugestão, outras tecnologias e métodos existem para salvar os dados, o importante é não misturar a lógica do jogo com métodos para gravar em banco/arquivos.

Camada controle

  • Possui uma coleção com todos os modelos (pode ter mais de um “modelo” sendo simulado, um “modelo” poderia ser uma cidade com todos os prédios ou um sistema solar com todos os planetas, ou uma pista com todos os carros).
  • Essa camada é responsável entre o link da camada física e lógica, criando um itemFísico para cada itemLógico registrado.
  • Responsável por atualizar a camada visão com todas as mudanças pertinentes ocorridas na camada lógica e física. Lembre que nem tudo é pertinente, afinal, para o jogador não é pertinente saber a posição de um objeto fora do campo de visão.
  • Responsável por receber chamadas da camada visão relativas as ações do usuário, por exemplo, o usuário acionou o input de “Atirar míssil”, esse input então é repassado ao respectivo Modelo.

A forma padrão de implementar a camada controle seria criando um ItemController para cada ItemLógico do jogo, não gostei disso pois ficaria muito subutilizado já que não teria dado algum nesse objeto. Preferi então criar um objeto Controller para a aplicação inteira e fiz algumas alterações para que o ItemController aceitasse qualquer Item.

Ah, a camada controle NÃO possui dados das entidades... ela serve como “meio de campo” apenas.

Inteligência Artificial: A IA até pode ficar na mesma camada do modelo, mas deve a camada de controle deve ser usada para enviar e receber informações sobre o estado do jogo. Idealmente a IA deve ser tratada como se fosse um jogador, com o mesmo tipo de informação que ele (pretendo elaborar melhor o tema em um artigo futuro).

Para que a IA possa trabalhar, eu sugiro criar métodos como esse:

public double executeQuery(ItemLogico currentEntityID, String query) {
    return QueryCentral.checkQuery(currentEntityID, query);
}

Uma “query” é uma pergunta que se faz ao modelo relacionado a entidade, relacionado a altuma coisa, por exemplo, se o Alvo está em alcance, se ele está à direita, esquerda, etc.

E para a IA enviar um comando, ela deve chamar um método similar ao que o usuário usaria:

public void activateControl(ItemLogico currentEntityID,String control){
    currentEntityID.activateKeyGroup(control);
}

E pronto, IA sem tratamento especial.

Para o meu projeto, um jogo chamado Galatic Craft, eu estou usando Behavior Trees... assunto para um próximo artigo, talvez.

Camada Visão (Client Gráfico)

  • Ao inicializar, a camada view se registra como um listener no controller, passando a ser notificada sempre que alguma propriedade é modifica.
  • Responsável por desenhar graficamente os objetos e o mundo em que os objetos estão e pelos efeitos que esses objetos geram (explosões, luzes, etc)
  • Responsável por traduzir inputs do usuário em comandos válidos (por ex, mapear a tecla L a “Lançar Míssil”).
  • Note que há várias similaridades entre a camada controller e a interface IView (registerItemVisual, unregister,etc), a diferença é que só existe um controller para a aplicação toda e vai haver um View para cada client conectado (e do lado do cliente).

Note que a camada Visão é a única que o jogador interage com, é normalmente a que tem maior impacto final no usuário... E é também a menos importante do ponto de vista do jogo/aplicação. Visualize essa camada como se fosse o vendedor de algum produto, é ele quem liga para o cliente e pega dados e repassa para a fábrica/equipe (engine do jogo) por algum meio (controller), mas definitivamente não é o vendedor que faz a fábrica trabalhar e a fábrica não deixa de funcionar se não tiver nenhum Vendedor. E, idealmente, podem ter vários vendedores trabalhando simultaneamente e com “métodos” diferentes.

A grande vantagem nisso é que é fácil substituir o “Vendedor”, você pode ter uma engine gráfica em 3D que está desatualizada e criar uma nova baseada na última tecnologia gráfica e sem reescrever uma linha sequer do jogo em si. Ou você pode mudar a forma como a fábrica opera sem precisar notificar o Vendedor, podendo terceirizar seções inteiras ou passando a implementar coisas que antes dependiam de terceiros de forma transparente.

Vantagens e desvantagens

À primeira vista parece complexo mas um código escrito dessa forma garante várias coisas, entre elas:

  • Reutilização de código, podendo reaproveitar o código em projetos diferentes.
  • O desacoplamento entre as camadas permite que cada desenvolvedor foque na área dele (por exemplo, um designer gráfico não precisa se preocupar em como é feito a lógica dos objetos já que são objetos separados).
  • Camada de apresentação (o client propriamente dito) pode ser feito em tecnologia totalmente diferente do restante da aplicação, por exemplo, em Flash/Flex/etc enquanto o backend é feito em Java/C++/etc.
  • Código mais claro e fácil de entender, manter e atualizar.
  • A separação em camadas oferece uma facilidade maior em criar aplicações multi-thread, podendo criar uma thread para a engine física, outra para a camada lógica e outra para a camada visual, a gosto do freguês e independentes entre si. Podendo ser distribuídas em servidores separados para aumentar o desempenho.
  • Como os dados do jogo não ficam no cliente, fica mais dificil para o jogador usar de cheats e exploits, já que dados importantes como “Life” e “Posição” são atualizados via mão-única (controller->View).

Claro que isso não vem sem algumas desvantagens:

  • Quantidade maior de objetos para representar cada entidade do jogo: ItemFísico->ItemLógico->(ItemController)<->ItemView
  • O começo do desenvolvimento é mais lento já que é preciso criar as camadas e as conexões entre elas antes de ter algum resultado prático.

A grande vantagem para o meu projeto, foi a separação dos dados da camada física da camada visual. Explico, estou implementando uma simulação espacial da forma mais real possível, e isso exige uma maior precisão matemática (afinal o espaço é enorme), optei então por usar variáveis do tipo Double (64-bits) e ao tentar desenhar... bom, os resultados não foram lá essas coisas, acontecendo o que é chamado de Z-Fighting:

Z-fighting

Isso ocorre porque as placas gráficas usam precisão menor (24 a 32-bits) o que causa várias inconsistências gráficas quando se usa valores muito altos.

(E a engine física também teve problemas similares, acabei fazendo uma a partir do livro “Game Physics Engine Development” mas isso é assunto para outro artigo)

Para resolver isso, eu implementei a camada Modelo e Física com precisão double e usei no cliente a posição das entidades subtraída da posição do jogador. Ou seja, a posição do jogador é sempre (0,0,0) e a posição das demais entidades é relativa a essa posição. Então, visualmente, quem move são os objetos ao redor. Problema resolvido!

Recomendo esse artigo para quem quiser se aprofundar no assunto:

http://www.oracle.com/technetwork/articles/javase/mvc-136693.html

Ele mostra uma implementação de MVC em Java de forma simples.

Vale lembrar que MVC não é específico para Java, podendo ser implementado em praticamente qualquer linguagem.

Conclusão

E é só isso tudo, espero que tenham gostado do tema abordado, resolvi tratar esse assunto para servir de base para os demais artigos que eu pretendo escrever. É um assunto a princípio chato mas tem grandes implicações na maturidade dos projetos, servindo de fundação para grandes jogos e sistemas. Bom, se tiverem alguma dúvida é só postar um comentário.


Comentários (8)
  • PotHix
    avatar

    Æ!!

    Conheço MVC há um tempo e tenho utilizado muito para o desenvolvimento de aplicações. Já vi alguns artigo sobre a utilização dele no desenvolvimento de jogos mas não gostei muito.

    Seria legal novos artigos criando um jogo simples utilizando MVC, quem sabe eu mudo de idéia. :)

    Há braços

  • Cristiano de Medeiros Ferreira
    avatar

    Oi PotHix,
    Posso preparar um artigo mais prático sobre o assunto, até pensei em implementar um jogo nesse artigo mas achei melhor uma abordagem mais independente de tecnologia explicando o conceito do MVC (sem falar que ia ficar meio confuso explicando MVC e o código).

    Bom, vou adicionar na minha lista de artigos futuros, uma implementação usando comunicação remota viria bem a calhar...

    sds,
    Cristiano

  • Alexsandro  - Muito bom
    avatar

    Muito bom o artigo, adoro o site, tem ajudado muito.!

  • Daniel Fróes  - Parabéns.
    avatar

    Legal o artigo, eu já li alguns, pena que sou péssimo com isso na prática. Então se sair algum mais prático me interessaria também! Vou tentar estudar pelo link que você passou que parece ser bem completo também.

  • dj++
    avatar

    mt boa a abordagem...para sistemas robustos e grandes games essa prática é obrigatória...estou adotando desenvolvimento por camadas no meu grupo de desenvolviento de games...isso faz parte da engenharia de softwere.

  • Bruno Crivelari Sanches
    avatar

    Não considero MVC obrigatório, muito menos para jogos, é uma boa arquitetura que se encaixa bem em alguns casos, principalmente em sistemas web.

    No caso de jogos acho uma alternativa boa, melhor que o "gameobject" que faz tudo e mais simples de implementar que um sistema de componentes (que atualmente é o considerado estado da arte), mas ainda prefiro o desenvolvimento via componentes para jogos, é meio chato no inicio, mas a flexibilidade ajuda muito no dia a dia.

    T+

  • marcia  - roli
    avatar

    B) :side: :S :)

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter