Implementar padrões de design comuns de programação de jogos em seu projeto Unity pode ajudá-lo a construir e manter uma base de código limpa, organizada e legível de forma eficiente. 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ócio.
Pense em padrões de design não como soluções prontas que você pode copiar e colar em seu código, mas como ferramentas extras que podem ajudá-lo a construir aplicações maiores e escaláveis.
Esta página explica o padrão de design de comando.
O conteúdo aqui é baseado no e-book gratuito, Nivele seu código com padrões de programação de jogos.
Confira mais artigos na série de padrões de design de programação de jogos Unity no hub Melhores práticas do Unity ou através destes links:
O padrão de design de programação de comando é um dos originais Gang of Four e é útil sempre que você quiser rastrear uma série específica de ações. Você provavelmente já viu o padrão de comando em ação se jogou um jogo que usa a funcionalidade de desfazer/refazer ou mantém seu histórico de entradas em uma lista. Imagine um jogo de estratégia onde o usuário pode planejar várias jogadas antes de realmente executá-las. 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 que você crie um sistema flexível e extensível para controlar o comportamento dos 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 uma pilha, que funciona como um pequeno buffer.
Armazenar objetos de comando dessa forma permite que você controle o tempo de sua execução, potencialmente atrasando uma série de ações para reprodução posterior. Da mesma forma, você pode refazer ou desfazer essas ações 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 construções. O jogo executaria cada comando à medida que os recursos se tornassem disponíveis.
- Em um jogo de estratégia por turnos, o jogador poderia selecionar uma unidade e, em seguida, armazenar seus movimentos ou ações em uma fila ou outra coleção. No final da rodada, o jogo executaria todos os comandos na fila do jogador.
- Em um jogo de quebra-cabeça, o padrão de comando poderia permitir que o jogador desfizesse e refizesse ações.
- Em um jogo de luta, ler os pressionamentos de botão ou os movimentos do controle em uma lista de comandos específica pode resultar em combos e movimentos especiais.
Experimente o projeto de amostra no GitHub que demonstra diferentes padrões de design de programação no contexto do desenvolvimento de jogos, incluindo o padrão de comando.
Neste exemplo, o jogador pode se mover por um labirinto clicando nos botões do lado esquerdo. À medida que seu jogador se move, você pode ver um rastro de movimento. Mas, mais importante, você pode desfazer e refazer suas ações anteriores.
Para encontrar a cena correspondente no projeto, vá para a pasta chamada "9 Command."
Para implementar o padrão de comando, você precisará de um objeto geral que conterá sua ação. Este objeto de comando conterá a lógica a ser executada e como desfazê-la.
Existem várias maneiras de implementar isso, mas aqui está uma versão simples usando uma interface chamada ICommand:
interface pública ICommand
{
void Executar();
void Undo();
}
Neste caso, cada ação de jogo aplicará a interface ICommand (você também poderia implementar isso com uma classe abstrata).
Cada objeto de comando será responsável por seus próprios Executar e Desfazer métodos. Portanto, adicionar mais comandos ao seu jogo não afetará nenhum existente.
A CommandInvoker classe é então responsável por executar e desfazer comandos. Além dos métodos ExecuteCommand e UndoCommand, possui uma pilha de desfazer para manter a sequência de objetos de comando.
No projeto de amostra, você pode mover seu jogador 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 no método Move para guiar o jogador nas quatro direções da bússola. Você também pode usar um raycast para detectar as paredes na LayerMask apropriada. Claro, implementar o que você quer aplicar ao padrão de comando é separado do próprio padrão.
Para seguir o padrão de comando, capture o método PlayerMover’s Move como um objeto. Em vez de chamar Move diretamente, crie uma nova classe, MoveCommand, que implementa a interface ICommand.
comando público de classe Move : Comando
{
MoverJogador playerMover;
Movimento Vector3;
comando de movimento público(PlayerMover jogador, Vector3 vetorDeMovimento)
{
this.playerMover = player;
this.movement = moveVector;
}
público void Executar()
{
playerMover.Move(movement);
}
público void Desfazer()
{
playerMover.Move(-movement);
}
}
Qualquer lógica que você deseja realizar vai aqui, então invoque Mover com o vetor de movimento.
Comando também precisa de um Desfazer método para restaurar a cena ao seu estado anterior. Neste caso, a Desfazer lógica subtrai o vetor de movimento, essencialmente empurrando o jogador na direção oposta.
O MoveCommand armazena quaisquer parâmetros que precisa para executar. Configure isso com um construtor. Neste caso, você salva o componente apropriado PlayerMover 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 CommandInvoker’s ExecuteCommand e UndoCommand para passar seu MoveCommand. Isto executa o MoveCommand’s Execute ou Undo 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 o 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 detalhes de implementação do InputManager. Você também pode configurar sua própria entrada usando o teclado ou o controle. Seu jogador agora pode navegar pelo labirinto. Clique no botão Desfazer para que você possa voltar ao quadrado inicial.
Implementar a re-jogabilidade ou a capacidade de 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 onde uma série de cliques em botões específicos aciona um movimento ou ataque em combo. Armazenar ações dos jogadores 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 oferecem benefícios suficientes para implantar objetos de comando em sua aplicação.
Uma vez que você aprenda o básico, você pode afetar o tempo dos comandos e reproduzi-los em sucessão ou em reverso, dependendo do contexto.
Considere o seguinte ao incorporar o padrão de comando:
- Crie mais comandos: O projeto de amostra 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 o CommandInvoker.
- Adicionar a funcionalidade de refazer é uma questão de adicionar outra pilha: Quando você desfaz um objeto de comando, empurre-o para uma pilha separada que rastreia 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 o seu buffer de objetos de comando: Uma fila pode ser mais conveniente se você quiser um comportamento de primeiro a entrar, primeiro a sair (FIFO). Se você usar uma lista, acompanhe o índice ativo atualmente; comandos antes do índice ativo são desfeitos. Os comandos após o índice podem ser refeitos.
- Limite o tamanho das pilhas: Desfazer e refazer operações podem rapidamente sair do controle. Limite as pilhas ao menor número de comandos.
- Passe quaisquer parâmetros necessários no construtor: Isso ajuda a encapsular a lógica, como 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. Dê ao objeto de comando os dados necessários para funcionar ao chamar o construtor.
Encontre mais dicas sobre como usar padrões de design em suas aplicações Unity, bem como os princípios SOLID, no e-book gratuito Eleve seu código com padrões de programação de jogos.
Você pode encontrar todos os e-books e artigos técnicos avançados de Unity no hub melhores práticas. Os e-books também estão disponíveis na página práticas recomendadas avançadas na documentação.