Você já se perguntou como em jogos como 
Super Meat Boy a função de repetição é implementada? Uma das maneiras de implementá-lo é executar a entrada da mesma maneira que o reprodutor, o que, por sua vez, significa que a entrada precisa ser armazenada de alguma forma. Você pode usar 
o padrão de comando para isso e muito mais.
O modelo de comando também é útil para criar funções de desfazer e refazer em um jogo de estratégia.
Neste tutorial, implementamos o modelo de comando em C # e o usamos para guiar o caractere bot através de um labirinto tridimensional. No tutorial, você aprenderá:
- Os princípios do padrão de comando.
- Como implementar o padrão de comando
- Como criar uma fila de comandos de entrada e atrasar sua execução.
Nota : supõe-se que você já esteja familiarizado com o Unity e tenha um conhecimento médio de C #. Neste tutorial, trabalharemos com o Unity 2019.1 e C # 7 .
Começando a trabalhar
Para começar, faça o download dos 
materiais do 
projeto . Descompacte o arquivo e abra o projeto 
Starter no Unity.
Vá para 
RW / Scenes e abra a cena 
principal . A cena consiste em um bot e um labirinto, bem como uma interface de usuário terminal que exibe instruções. O design do nível é feito na forma de uma grade, o que é útil quando movemos o bot visualmente pelo labirinto.
Se você clicar em 
Play , veremos que as instruções não funcionam. Isso é normal porque adicionaremos essa funcionalidade ao tutorial.
A parte mais interessante da cena é o GameObject 
Bot . Selecione-o na janela Hierarquia clicando nele.
No Inspetor, você pode ver que ele possui um componente 
Bot . Usaremos esse componente emitindo comandos de entrada.
Entendemos a lógica do bot
Vá para 
RW / Scripts e abra o script 
Bot no editor de código. Você não precisa saber o que está acontecendo no script 
Bot . Mas dê uma olhada em dois métodos: 
Move e 
Shoot . Novamente, você não precisa descobrir o que está acontecendo dentro desses métodos, mas precisa entender como usá-los.
Observe que o método 
Move recebe um parâmetro de entrada 
CardinalDirection . 
CardinalDirection é uma enumeração. Um elemento de enumeração do tipo 
CardinalDirection pode ser 
Up , 
Down , 
Right ou 
Left . Dependendo da 
CardinalDirection selecionada 
CardinalDirection bot se move exatamente um quadrado ao longo da grade na direção correspondente.
O método 
Shoot força o bot a disparar conchas que destroem as 
paredes amarelas , mas são inúteis contra outras paredes.
Por fim, dê uma olhada no método 
ResetToLastCheckpoint ; para entender o que ele está fazendo, olhe para o labirinto. Há pontos no labirinto chamado 
ponto de verificação . Para passar pelo labirinto, o bot precisa chegar ao ponto de controle 
verde .
Quando um bot pisa em um novo ponto de controle, ele se torna o 
último para ele. 
ResetToLastCheckpoint redefine a posição do bot, movendo-o para o último ponto de controle.
Embora não possamos usar esses métodos, vamos corrigi-lo em breve. Para começar, você precisa aprender sobre o padrão de design do 
Comando .
O que é o Command Design Pattern?
O padrão Command é um dos 23 padrões de design descritos no livro 
Design Patterns: Elements of Reusable Oriented Object Oriented, escrito por Gang of Four por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides ( 
GoF , Gang of Four).
Os autores relatam que "o padrão Command encapsula a solicitação como um objeto, permitindo assim parametrizar outros objetos com solicitações diferentes, solicitações de fila ou log e oferecer suporte a operações reversíveis".
Uau! Como é isso?
Entendo que essa definição não é muito simples, então vamos analisá-la.
Encapsulamento significa que uma chamada de método pode ser encapsulada como um objeto.
O método encapsulado pode afetar muitos objetos, dependendo do parâmetro de entrada. Isso é chamado de 
parametrização de outros objetos.
O "comando" resultante pode ser salvo junto com outras equipes até que sejam executados. Esta é a 
fila de pedidos.
Fila de equipeFinalmente, 
reversibilidade significa que as operações podem ser revertidas usando a função Desfazer.
OK, mas como isso se reflete no código?
A classe 
Command terá um método 
Execute , que recebe como parâmetro de entrada o objeto (pelo qual o comando é executado) chamado 
Receiver . Na verdade, o método Execute é 
encapsulado pela classe Command.
Muitas instâncias da classe Command podem ser passadas como objetos comuns, ou seja, elas podem ser armazenadas em estruturas de dados como uma fila, pilha, etc.
Para executar um comando, você deve chamar seu método Execute. A classe que inicia a execução é chamada 
Invoker .
O projeto atualmente contém uma classe vazia chamada 
BotCommand . Na próxima seção, implementaremos a implementação acima para permitir que o bot execute ações usando o modelo de comando.
Mover o bot
Implementação de padrão de comando
Nesta seção, implementamos o padrão de comando. Existem muitas maneiras de implementá-lo. Neste tutorial, abordaremos um deles.
Para começar, vá para 
RW / Scripts e abra o script 
BotCommand no editor. A classe 
BotCommand ainda 
BotCommand vazia, mas não por muito tempo.
Insira o seguinte código na classe:
   
O que está acontecendo aqui?
- A variável commandNameusada simplesmente para armazenar o nome do comando legível por humanos. Não é necessário usá-lo no modelo, mas precisaremos posteriormente no tutorial.
- O construtor do BotCommandrecebe uma função e uma string. Isso nos ajudará a configurar o métodoExecutedo objeto Command e seuname.
- O representante ExecuteCallbackdefine o tipo de método encapsulado. O método encapsulado retornará nulo e aceitará como parâmetro de entrada um objeto do tipoBot(componente Bot ).
- A propriedade Executefará referência ao método encapsulado. Vamos usá-lo para chamar o método encapsulado.
- O método ToStringé substituído para retornar a cadeiacommandName. Isso é conveniente, por exemplo, para uso na interface do usuário.
Salve as alterações e pronto! Implementamos com sucesso o padrão de comando.
Resta usá-lo.
Team building
Abra o 
BotInputHandler na pasta 
RW / Scripts .
Aqui criaremos cinco instâncias do 
BotCommand . Essas instâncias encapsularão métodos para mover o GameObject Bot para cima, para baixo, para a esquerda e para a direita, bem como para fotografar.
Para implementar isso, insira o seguinte nesta classe:
   
Em cada uma dessas instâncias, 
um método anônimo é passado para o construtor. Este método anônimo será encapsulado dentro do objeto de comando correspondente. Como você pode ver, a assinatura de cada um dos métodos anônimos atende aos requisitos especificados pelo delegado 
ExecuteCallback .
Além disso, o segundo parâmetro para o construtor é uma sequência que indica o nome do comando. Este nome será retornado pelo método 
ToString da instância do comando. Mais tarde vamos aplicá-lo à interface do usuário.
Nas quatro primeiras instâncias, métodos anônimos chamam o método 
Move no objeto 
bot . No entanto, seus parâmetros de entrada são diferentes.
Os 
MoveUp , 
MoveDown , 
MoveLeft e 
MoveRight passam os parâmetros 
Move CardinalDirection.Up , 
CardinalDirection.Down , 
CardinalDirection.Left e 
CardinalDirection.Right . Conforme mencionado na seção 
O que é padrão de design de comando , eles indicam direções diferentes para o GameObject Bot se mover.
Na quinta instância, o método anônimo chama o método 
Shoot para o objeto 
bot . Graças a isso, o bot disparará um shell durante a execução do comando.
Agora que criamos os comandos, precisamos acessá-los de alguma forma quando o usuário faz uma entrada.
Para fazer isso, 
BotInputHandler seguinte código no 
BotInputHandler , imediatamente após as instâncias de comando:
  public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; } 
O método 
HandleInput retorna uma instância do comando, dependendo da tecla pressionada pelo usuário. Salve suas alterações antes de prosseguir.
Aplicando comandos
Ótimo, agora é hora de usar as equipes que criamos. Vá para 
RW / Scripts novamente e abra o script 
SceneManager no editor. Nesta classe, você notará um link para uma variável 
uiManager do tipo 
UIManager .
A classe 
UIManager fornece métodos auxiliares úteis para a 
interface do usuário do 
terminal que usamos nesta cena. Se o método do 
UIManager for usado, o tutorial explicará o que ele faz, mas, em geral, para nossos propósitos, não é necessário conhecer sua estrutura interna.
Além disso, a variável 
bot refere-se ao componente bot anexado ao GameObject 
Bot .
Agora adicione o seguinte código à classe 
SceneManager , substituindo-o pelo comentário 
//1 :
   
Uau, quanto código! Mas não se preocupe; finalmente estamos prontos para o primeiro lançamento real do projeto na janela do jogo.
Vou explicar o código mais tarde. Lembre-se de salvar as alterações.
Executando o jogo para testar o modelo de comando
Então agora é a hora de construir; Clique em 
Play no editor do Unity.
Você deve poder inserir comandos de movimentação usando 
as teclas WASD . Para inserir o comando de disparo, pressione a tecla 
F. Para executar comandos, pressione 
Enter .
Nota : até que o processo de execução seja concluído, não é possível inserir novos comandos.
Observe que as linhas são adicionadas à interface do usuário do terminal. As equipes na interface do usuário são indicadas por seus nomes. Isso é possível graças à variável 
commandName .
Observe também como a interface do usuário rola antes da execução e como as linhas são excluídas durante a execução.
Estudamos as equipes mais de perto
É hora de aprender o código que adicionamos na seção "Aplicando comandos":
- A lista botCommandsarmazena links para instâncias doBotCommand. Lembre-se de que, para economizar memória, podemos criar apenas cinco instâncias de comandos, mas pode haver várias referências a um comando. Além disso, a variávelexecuteCoroutinerefere-se aExecuteCommandsRoutine, que controla a execução do comando.
- Updateverifica se o usuário pressionou a tecla Enter;- ExecuteCommandscaso, chama- ExecuteCommands, caso contrário,- CheckForBotCommandsé- CheckForBotCommands.
- CheckForBotCommandsusa o método estático- HandleInputdo- BotInputHandlerpara verificar se o usuário concluiu a entrada e, em caso afirmativo, o comando é retornado . O comando retornado é passado para- AddToCommands. No entanto, se os comandos forem executados, ou seja, se- executeRoutinenão- executeRoutinenulo, ele retornará sem passar nada para- AddToCommands. Ou seja, o usuário precisa esperar até a conclusão.
- AddToCommandsadiciona um novo link à instância retornada do comando em- botCommands.
- O método InsertNewTextclasseInsertNewTextadiciona uma nova linha de texto à interface do usuário do terminal. Uma sequência de texto é uma sequência passada como um parâmetro de entrada. Nesse caso, passamos commandName paracommandName.
- O método ExecuteCommandsRoutineiniciaExecuteCommandsRoutine.
- ResetScrollToTopno- UIManagerrola a interface do usuário do terminal para cima. Isso é feito imediatamente antes do início da execução.
- ExecuteCommandsRoutinecontém um loop- forque itera sobre os comandos dentro da lista- botCommandse os executa um por um, passando o objeto- botpara o método retornado pela propriedade- Execute. Após cada execução, uma pausa é adicionada em segundos de- CommandPauseTime.
- O método RemoveFirstTextLinedoUIManagerexclui a primeira linha de texto na interface do usuário do terminal, se existir. Ou seja, quando um comando é executado, seu nome é removido da interface do usuário.
- Depois que todos os comandos são botCommandsé limpo e o bot é redefinido para o último ponto de interrupção usandoResetToLastCheckpoint. No final,executeRoutinenulle o usuário pode continuar a digitar comandos.
Implementando os recursos Desfazer e Refazer
Execute a cena novamente e tente chegar ao ponto de controle verde.
Você notará que, embora não possamos cancelar o comando digitado. Isso significa que, se você cometer um erro, não poderá voltar antes de concluir todos os comandos inseridos. Você pode corrigir isso adicionando os recursos 
Desfazer e 
Refazer .
Volte ao 
SceneManager.cs e adicione a seguinte declaração de variável imediatamente após a declaração 
List para 
botCommands :
  private Stack<BotCommand> undoStack = new Stack<BotCommand>(); 
A variável 
undoStack é uma 
pilha (da família Coleções) que armazenará todas as referências a comandos que podem ser desfeitos.
Agora, adicionamos dois métodos 
UndoCommandEntry e 
RedoCommandEntry que executarão Desfazer e Refazer. Na classe 
SceneManager , 
SceneManager seguinte código após 
ExecuteCommandsRoutine :
  private void UndoCommandEntry() {  
Vamos analisar o código:
- Se os comandos forem executados ou a lista botCommandsvazia, o métodoUndoCommandEntrynada. Caso contrário, ele grava um link para o último comando digitado na pilhaundoStack. Isso também remove o link para o comando da listabotCommands.
- O método RemoveLastTextLinedoUIManagerremove a última linha de texto da interface do usuário do terminal para que ela corresponda ao conteúdo debotCommands.
- Se a pilha undoStackvazia, oRedoCommandEntrynãoRedoCommandEntrynada. Caso contrário, ele extrai o último comando da parte superior doundoStacke o adiciona de volta à listaAddToCommandsusandoAddToCommands.
Agora vamos adicionar a entrada do teclado para usar essas funções. Dentro da classe 
SceneManager substitua o corpo 
Update método 
Update pelo seguinte código:
  if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U))  
- Quando você pressiona a tecla U , o método UndoCommandEntryéUndoCommandEntry.
- Quando você pressiona a tecla R , o método RedoCommandEntryéRedoCommandEntry.
Manuseio de Caixas de Borda
Ótimo, estamos quase terminando! Mas primeiro, precisamos fazer o seguinte:
- Ao inserir um novo comando, a pilha undoStackdeve ser limpa.
- Antes de executar comandos, a pilha undoStackdeve ser limpa.
Para implementar isso, primeiro precisamos adicionar um novo método ao 
SceneManager . Insira o seguinte método após 
CheckForBotCommands :
  private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); } 
Este método limpa 
undoStack e chama o método 
AddToCommands .
Agora substitua a chamada para 
AddToCommands dentro de 
CheckForBotCommands pelo seguinte código:
  AddNewCommand(botCommand); 
Em seguida, insira a seguinte linha após a 
if no método 
ExecuteCommands para limpar antes de executar os comandos 
undoStack :
  undoStack.Clear(); 
E finalmente terminamos!
Salve seu trabalho. Crie o projeto e clique no editor 
Play . Digite os comandos como antes. Pressione 
U para cancelar os comandos. Pressione 
R para repetir os comandos cancelados.
Tente chegar ao ponto de verificação verde.
Para onde ir a seguir?
Para saber mais sobre os padrões de design usados na programação de jogos, recomendo que você estude os 
Padrões de programação de jogos de Robert Nystrom.
Para saber mais sobre técnicas avançadas de C #, faça o curso 
C # Collections, Lambdas e LINQ .
Tarefa
Como tarefa, tente chegar ao ponto de controle verde no final do labirinto. Eu escondi uma das soluções embaixo do spoiler.
Solução- moveUp × 2
- moveRight × 3
- moveUp × 2
- moveLeft
- atirar
- moveLeft × 2
- moveUp × 2
- moveLeft × 2
- moveDown × 5
- moveLeft
- atirar
- moveLeft
- moveUp × 3
- atirar × 2
- moveUp × 5
- moveRight × 3