O que você está procurando?
Hero background image
Use o padrão de comando para sistemas de jogo flexíveis e extensíveis

A implementação de padrões comuns de design de programação de jogos em seu projeto Unity pode ajudá-lo a construir e manter com eficiência uma base de código limpa, organizada e legível. Os padrões de design reduzem o tempo de refatoração e teste, acelerando os processos de desenvolvimento e contribuindo para uma base sólida para o crescimento do seu jogo, equipe e negócios.

Pense nos padrões de design não como soluções acabadas que você pode copiar e colar em seu código, mas como ferramentas extras que podem ajudá-lo a criar aplicativos maiores e escaláveis.

Esta página explica o padrão de design do comando.

O conteúdo aqui é baseado no e-book gratuito Aumente o nível do seu código com padrões de programação de jogos.

Confira mais artigos da série de padrões de design de programação de jogos do Unity no hub de práticas recomendadas do Unity ou por meio destes links:

Compreendendo o padrão de comando

O padrão de design de programação de comandos é um do Gang of Four original e é útil sempre que você deseja rastrear uma série específica de ações. Você provavelmente já viu o padrão de comando em funcionamento se jogou um jogo que usa a funcionalidade de desfazer/refazer ou mantém seu histórico de entrada em uma lista. Imagine um jogo de estratégia onde o usuário pode planejar vários turnos antes de realmente executá-los. Esse é o padrão de comando.

O padrão de comando permite que ações sejam representadas como objetos. Encapsular ações como objetos permite criar um sistema flexível e extensível para controlar o comportamento de GameObjects em resposta à entrada do usuário. Isso funciona encapsulando uma ou mais chamadas de método como um “objeto de comando” em vez de invocar um método diretamente. Então você pode armazenar esses objetos de comando em uma coleção, como uma fila ou pilha, que funciona como um pequeno buffer.

Armazenar objetos de comando dessa forma permite controlar o tempo de sua execução, atrasando potencialmente uma série de ações para reprodução posterior. Da mesma forma, você pode refazê-los ou desfazê-los e adicionar flexibilidade extra para controlar a execução de cada objeto de comando.

Aqui estão algumas aplicações comuns do padrão em diferentes gêneros de jogos:

  • Em um jogo de estratégia em tempo real, o padrão de comando pode ser usado para enfileirar ações de unidades e edifícios. O jogo então executaria cada comando conforme os recursos ficassem disponíveis.
  • Em um jogo de estratégia baseado em turnos, o jogador poderia selecionar uma unidade e então armazenar seus movimentos ou ações em uma fila ou outra coleção. Ao final do turno, o jogo executaria todos os comandos da fila do jogador.
  • Em um jogo de quebra-cabeça, o padrão de comando poderia permitir ao jogador desfazer e refazer ações.
  • Em um jogo de luta, ler o pressionamento de botões ou movimentos do gamepad em uma lista de comandos específica pode resultar em combos e movimentos especiais.
Padrão de comando em um projeto de amostra
Padrão de comando em um projeto de amostra

Experimente o projeto de amostra no GitHub que demonstra diferentes padrões de design de programação no contexto de desenvolvimento de jogos, incluindo o padrão de comando.

Neste exemplo, o jogador pode mover-se por um labirinto clicando nos botões do lado esquerdo. Conforme o jogador se move, você pode ver uma trilha de movimento. Mas o mais importante é que você pode desfazer e refazer suas ações anteriores.

Para encontrar a cena correspondente no projeto, vá até a pasta chamada “9 Command”.

O objeto de comando e invocador de comando

Para implementar o padrão de comando, você precisará de um objeto geral que conterá sua ação. Este objeto de comando conterá qual lógica executar e como desfazê-la.

Existem diversas maneiras de implementar isso, mas aqui está uma versão simples usando uma interface chamada ICommand:

interface pública ICommand
{
void Executar();
void Desfazer();
}

Neste caso, cada ação do jogo aplicará a interface ICommand (você também pode implementar isso com uma classe abstrata).

Cada objeto de comando será responsável por seus próprios métodos Execute e Undo . Portanto, adicionar mais comandos ao seu jogo não afetará nenhum dos já existentes.

A classe CommandInvoker é então responsável por executar e desfazer comandos. Além dos métodos ExecuteCommand e UndoCommand , possui uma pilha de desfazer para armazenar a sequência de objetos de comando.

Exemplo: Movimento reversível

No projeto de exemplo você pode mover seu player por um pequeno labirinto. Uma opção simples para mudar a posição do jogador é criar um PlayerMover.

Para fazer isso, você precisará passar um Vector3 para o método Move para guiar o jogador ao longo das quatro direções da bússola. Você também pode usar um raycast para detectar as paredes na LayerMask apropriada. É claro que a implementação do que você deseja aplicar ao padrão de comando é separada do próprio padrão.

O comando Mover
O COMMANDINVOKER, ICOMMAND E MOVECOMMAND
O comando Mover

Para seguir o padrão de comando, capture o método Move do PlayerMover como um objeto. Em vez de chamar Move diretamente, crie uma nova classe, MoveCommand, que implemente a interface ICommand .

classe pública MoveCommand: Eu mando
{
PlayerMover playerMover;
Movimento do vetor3;
MoveCommand público (player PlayerMover, Vector3 moveVector)
{
this.playerMover = player;
this.movement = moveVector;
}
público vazio Executar()
{
playerMover.Move(movement);
}
público vazio Desfazer()
{
playerMover.Move(-movement);
}
}

Qualquer lógica que você queira realizar entra aqui, então invoque Move com o vetor de movimento.

ICommand também precisa de um método Undo para restaurar a cena ao seu estado anterior. Neste caso, a lógica Undo subtrai o vetor de movimento, essencialmente empurrando o jogador na direção oposta.

O MoveCommand armazena todos os parâmetros necessários para executar. Configure-os com um construtor. Nesse caso, você salva o componente PlayerMover apropriado e o vetor de movimento.

Depois de criar o objeto de comando e salvar os parâmetros necessários, use os métodos estáticos ExecuteCommand e UndoCommand do CommandInvoker para passar seu MoveCommand. Isso executa Executar ou Desfazer do MoveCommand e rastreia o objeto de comando na pilha de desfazer.

O InputManager não chama o método Move do PlayerMover diretamente. Em vez disso, adicione um método extra, RunMoveCommand, para criar um novo MoveCommand e enviá-lo para CommandInvoker.

Em seguida, configure os vários eventos onClick dos botões da UI para chamar RunPlayerCommand com os quatro vetores de movimento.

Confira o projeto de amostra para obter detalhes de implementação do InputManager. Você também pode configurar sua própria entrada usando o teclado ou gamepad. Seu jogador agora pode navegar pelo labirinto. Clique no botão Desfazer para poder voltar ao quadrado inicial.

Prós e contras

Implementar a capacidade de reprodução ou desfazer é tão simples quanto gerar uma coleção de objetos de comando. Você também pode usar o buffer de comando para reproduzir ações em sequência com controles específicos.

Por exemplo, pense em um jogo de luta em que uma série de cliques em botões específicos desencadeia um movimento ou ataque combinado. Armazenar as ações do jogador com o padrão de comando torna a configuração desses combos muito mais simples.

Por outro lado, o padrão de comando introduz mais estrutura, assim como os outros padrões de design. Você terá que decidir onde essas classes e interfaces extras fornecem benefícios suficientes para implantar objetos de comando em seu aplicativo

Depois de aprender o básico, você poderá afetar o tempo dos comandos e reproduzi-los em sucessão ou inversamente, dependendo do contexto.

Considere o seguinte ao incorporar o padrão de comando:

  • Crie mais comandos: O projeto de exemplo inclui apenas um tipo de objeto de comando, o MoveCommand. Você pode criar qualquer número de objetos de comando que implementem ICommand e rastreá-los usando CommandInvoker.
  • Adicionar a funcionalidade de refazer é uma questão de adicionar outra pilha: Ao desfazer um objeto de comando, coloque-o em uma pilha separada que rastreia as operações de refazer. Dessa forma, você pode percorrer rapidamente o histórico de desfazer ou refazer essas ações. Limpe a pilha de refazer quando o usuário invocar um movimento totalmente novo (você pode encontrar uma implementação no projeto de exemplo).
  • Use uma coleção diferente para seu buffer de objetos de comando: Uma fila pode ser mais prática se você quiser o comportamento primeiro a entrar, primeiro a sair (FIFO). Se você usar uma lista, acompanhe o índice atualmente ativo; comandos antes do índice ativo são desfazíveis. Os comandos após o índice podem ser refeitos.
  • Limite o tamanho das pilhas: As operações de desfazer e refazer podem rapidamente ficar fora de controle. Limite as pilhas ao menor número de comandos.
  • Passe quaisquer parâmetros necessários para o construtor: Isso ajuda a encapsular a lógica conforme visto no exemplo MoveCommand.
  • O CommandInvoker, como outros objetos externos, não vê o funcionamento interno do objeto de comando, apenas invocando Executar ou Desfazer. Forneça ao objeto de comando todos os dados necessários para funcionar ao chamar o construtor.
e-book azul
Mais recursos

Encontre mais dicas sobre como usar padrões de design em seus aplicativos Unity, bem como os princípios SOLID, no e-book gratuito Aumente seu código com padrões de programação de jogos.

Você pode encontrar todos os e-books e artigos técnicos avançados do Unity no hub de práticas recomendadas . Os e-books também estão disponíveis na página de práticas recomendadas avançadas na documentação.

Você gostou deste conteúdo?