Ponto V!

Home Arquitetura Programação Programação de Gameplay 101
Bruno Crivelari Sanches
Programação de Gameplay 101Imprimir
Escrito por Bruno Crivelari Sanches

Neste artigo, tenho como objetivo cobrir as estruturas básicas e os padrões mais comuns usados para se programar a lógica de um jogo ou gameplay. Não pretendo discutir aqui técnicas gráficas ou como fazer efeitos especiais, mas conceitos básicos de como fazer, por exemplo, um npc caminhar até um determinado ponto e executar uma ação, abrir uma porta, etc. Pretendo abranger as técnicas de forma abstrata, sem focar em um motor ou tecnologia especifica, de forma que os conceitos aqui mostrados possam ser aplicados tanto por quem esta construindo seu próprio motor ou por desenvolvedores usando ferramentas de alto nível, como Unity.

Esse tipo de programação pode ser feita da maneira “hardcoded”, onde para cada nível do jogo, o programador escreve um código especifico para certas ações ou um script, mas utilizar esse tipo de técnica possui alguns inconvenientes, sendo os principais:

  • Não é possível visualizar de forma gráfica o que esta sendo criado.
  • O trabalho de level design acaba fazendo parte do trabalho do programador e o ideal é que os designers ou level designers possam testar ou criar o máximo possível sem precisar da assistência de um programador.
  • No pior dos casos, utilizando linguagens compiladas, vai ser preciso compilar todo projeto para se testar pequenas alterações no design de cenário.

A partir deste ponto, assumo que o desenvolvedor tem a sua disposição algum tipo de editor de níveis, não confundir aqui, especialmente para quem estiver lidando com jogos 3d construção de cena com design de níveis. Modelar um cenário é apenas uma parte do processo, após a modelagem, é preciso “dar vida ao jogo” preenchendo o cenário com os objetos funcionais. Pode-se até mesmo utilizar um simples arquivo de texto para definir o nível do jogo, como mostrado aqui.

Outro requisito deste artigo é ter uma noção de design “data driven”, conforme discutido neste artigo, pois aqui iremos mostrar de forma um pouco mais prática como fazer isso.

Cenário Exemplo

image

Na figura acima temos um nível bem simples (feito no editor do jogo Frogatto & Friends) que é usado como base no restante do artigo. O boneco na esquerda é o jogador. Para passar de fase, o jogador precisa subir na parte de cima e puxar a alavanca, que abre a porta. Apesar de toda simplicidade, para modelarmos esse nível vamos ter um trabalho razoável, mas criados os componentes básicos, fica fácil expandir e fazermos construções mais complexas.

Estrutura Básica

Não existe uma regra ou manual de como construir um jogo, por isso, aqui iremos definir a estrutura básica do jogo, que consiste em:

  • O jogo ou motor possuem uma lista de objetos do jogo e esses objetos podem ser acessados de alguma forma usando um identificador ou nome.
  • Os objetos do jogo, neste artigo vão ser chamados de entidades, termo utilizado em diversas tecnologias para se referir aos componentes iterativos de um jogo.
  • O jogo possui algum mecanismo de colisão, que notifica uma entidade que outra entidade esta colidindo com ela.

A estrutura descrita acima, apesar de ser simples, pode ser implementada de inúmeras formas e não vamos discutir os detalhes da implementação aqui. Caso os leitores sintam a necessidade de uma descrição mais detalhada, isso pode ser discutido em um artigo futuro.

Estrutura de Classes

Apesar de um sistema de componentes ser muito mais poderoso que uma simples hierarquia de classes, a estrutura de classes é mais simples de visualizar e de se explicar, dessa forma este artigo vai ser baseado numa estrutura de classes, que pode ser visualizada a seguir:

Diagrama de classes do jogo

No diagrama acima podemos ver as classes que são necessárias para se criar o cenário mostrado anteriormente, todas derivam da classe Entity (Entidade) e esta determina a funcionalidade básica que todos os objetos do jogo vão suportar.

Um jogo finalizado, certamente vai ter muito mais classes e a classe entidade geralmente precisa de muitos mais métodos e atributos, mas para simplificar mostramos apenas os itens realmente relevantes.

Observe que cada entidade possui um nome (descrito pelo atributo name), isso é usado para que objetos possam identificar uns aos outros e é assumido que os nomes são únicos. Não é preciso utilizar uma string com o nome dos objetos, um simples identificador numérico como o índice em um array já seria suficiente. A vantagem maior de se trabalhar com strings é que fica mais intuitivo estruturar um nível podendo dar nomes as coisas, o que muitas vezes supera o custo computacional extra de ter que lidar com strings.

Como Armazenar o Nível

A estrutura de funcionamento é relativamente simples, no editor de níveis do jogo, quando o usuário criar uma alavanca como mostrado na imagem acima, ele tem que indicar qual o alvo dela, no caso, o nome de outro objeto do jogo. Digamos que a porta seja nomeada como “porta_saída”, no campo “target” do objeto Switch, o usuário coloca então “porta_saída” no campor “target” da alavanca.

O editor de níveis salva isso de alguma forma, pode-se usar XML, JSon ou qualquer outro formato, em JSON poderíamos ter:

[
    {
        "type":"Door",
        "name":"porta_saída"

        //outros atributos que forem relevantes, exemplos:
        "speed":10,
        "direction":"up"
        //etc
    },
    {
        "type":"Switch",
        "name":"alavanca_abre_saída",
        "target":"porta_saída",
        //etc
    },
    {
        //etc
    }    
]

De alguma forma seu jogo vai carregar os dados do editor e não cabe discutir formas de fazer neste artigo, a única dica é ter dentro do sistema algum tipo de Factory que seja possível a partir do tipo do objeto, criar uma instância dele.

Exemplo: o código responsável por carregar o nível, vai percorrer objeto a objeto do arquivo JSON e inspecionar o parâmetro type, no primeiro objeto do código acima o valor de type é “Door”, uma string que indica que o tipo de objeto é uma porta, o código então solicita a factory que crie o objeto desse tipo e delega ao objeto recém-criado o trabalho de carregar os demais atributos, podemos ver o processo em mais detalhes no diagrama de sequencia abaixo:

Exemplo de processo de carregamento de um nível

Este processo continua até que todo nível seja carregado.

Fazendo o Nível Funcionar

Como as entidades já foram todas criadas e estão prontas para serem usadas, o jogo volta (ou inicia) o loop principal e as entidades criadas durante o carregamento do nível vão então fazer… nada. Isso mesmo, elas por enquanto não fazem nada, pois todas elas são apenas reativas, só fazem algo quando um evento ocorre. Existem alguns tipos que começam a trabalhar desde o inicio do nível, mas isso fica para o próximo artigo.

O jogador então vai se movimentar pelo nível, se tentar ir até a porta, nada acontece. A porta pode até receber o evento de que foi “tocada” pelo jogador, mas como ela esta associada a uma alavanca, não vai fazer nada. Pode até ser desejável, ela exibir uma mensagem para o jogador, que é comum em muitos jogos ou emitir um som de trancado, mas isso fica a cargo dos designers decidirem a melhor forma de lidar com esse problema.

Quando o jogador for à parte superior do nível e tocar na alavanca, eventos interessantes ocorrem: o método “onTouch” da alavanca vai ser invocado, pois ela foi tocada pelo jogador. A alavanca requisita ao gerenciador de entidades o objeto com o nome especificado no seu campo “target”, ao receber o objeto a alavanca então invoca seu método “onActivate”, nesse caso, o método invocado vai ser o da porta, que então vai se abrir. No diagrama abaixo mostramos o processo em execução:

Sequencia de eventos quando o jogador toca a alavanca

Observe que apenas quando a alavanca precisa invocar “seu alvo” é que ela resolve a referência para o nome do objeto no seu campo “target”, isso é proposital, pois evista problemas como uma alavanca possuir um ponteiro para um objeto que já foi detruído.

Iteração do jogador com objetos do Jogo

No processo acima foi descrito como o jogo reage quando o jogador colide com uma entidade do jogo, mas como o jogo sabe que isso ocorreu? Este é mais um item que não possui resposta e depende muito do jogo. A maneira mais trivial de se implementar isso é no loop principal do jogo, sempre que for atualizada a posição da entidade do jogador, verificar se ele colidiu com algum objeto, se sim, notificar esses objetos.

Este processo pode ser feito para todos os objetos que se movem, mas gera um problema: como fazer, por exemplo, que um monstro não ative uma alavanca ao passar por ela? Para isso, uma solução possível é indicar em cada objeto com o que ele colide. Isso pode ser feito por tags, mascaras de bits, etc.

Considerações Finais

A técnica e o exemplo mostrado aqui a principio são relativamente simples, mas com pequenas adições, podemos criar comportamentos mais complexos, alguns exemplos:

  • Pode-se criar uma classe “Espinho”, que no método “onTouch” tira um pouco de energia do jogador, para simular que ele tenha se ferido.
  • A porta pode ter alguns flags, que indicam, por exemplo, se ela começa aberta ou fechada e tocando a alavanca, a porta se fecha ao invés de abrir.
  • A alavanca pode ter um flag indicando que ela funciona uma única vez, isso impediria o jogador de ficar abrindo e fechando portas.
  • Ao invés da alavanca, podemos ter um objeto invisível (que pode ser visto apenas no editor) e o jogador ao passar por ele, faz a ativação, criando-se assim o efeito de uma porta se abrir quando o jogador se aproxima ou até mesmo iniciar uma cut-scene.

Existem inúmeros outros objetos que podem ser criados para se criar o mecanismo de um nível, o principal é observar a modularidade do sistema, que minimiza a quantidade de objetos a serem criados e possibilidade ao level designer ir montando comportamentos complexos apenas encadeando objetos e eventos, sem a necessidade de auxilio de um programador.


Comentários (10)
  • Eddy  - Jogo arcade
    avatar

    Gostaria de saber se exite algum tutorial sobre desenvolvimento de um game na perspectiva dos jogos Crash Bandicoot

  • Bruno Crivelari Sanches
    avatar

    Não vejo nada de especial nesse jogo, é um jogo de terceira pessoa e nada além disso. As técnicas mostradas aqui eu apliquei no jogo Jessy (que você pode ver no meu perfil) que ao meu ver era praticamente a mesma coisa, a diferença que a Jessy era para crianças e não tinha tanta ação.

    T+

  • Paulo V W Radtke  - Show!
    avatar

    Muito bom o artigo, quanto mais parametrizado por fora for o jogo, melhor ele fica de se trabalhar.

  • Bruno Crivelari Sanches
    avatar

    Fica muito fácil desenvovler assim PV, quando o pessoal vai falar de módulos e desenvolvimento OO em cursos, deveriam olhar com mais carinho a certos jogos :).

  • Dev
    avatar

    Vc usou o Eclipse nesse game??

  • Bruno Crivelari Sanches
    avatar

    Que jogo? O Frogatto do artigo? Não, nem fui eu quem fiz, peguei apenas como exemplo.

    Se for a Jessy que comento ali, não, Visual Studio, não gosto do Eclipse.

  • Dev
    avatar

    Eu ja sou ao contrario!! odeio Visual studio!! XNA ecaaaaaa !! godto é gosto T+

  • Anônimo
    avatar

    Usar o visual studio não lhe obriga a usar XNA.

  • Emerson Max
    avatar

    Dou maior valor quando vejo um artigo mostrando algum diagrama de classe ou sequencia, isso deixa mais claro as ideias que o autor quer passar.

    Muito bom, continue assim xD

  • Bruno Crivelari Sanches
    avatar

    Obrigado Emerson!

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