Unity3D Design Patterns – State – Building a Unity3D State Machine

  • Home /
  • Unity3D
  • / Unity3D Design Patterns – State – Building a Unity3D State Machine
By Jason Weimann /May 26, 2017

Padrões de design são soluções reutilizáveis para problemas comuns encontrados em projetos de software. Usar bons padrões de design na Unidade pode ajudar a manter o seu projeto limpo, de fácil manutenção e de fácil extensão. Também os torna mais fáceis de construir… Hoje, vou cobrir o padrão de estado e os fundamentos da construção de uma máquina de estado na Unidade3D.

O padrão de estado é um padrão de design de software comportamental que implementa uma máquina de estado de uma forma orientada a objetos. … Este padrão é usado em programação de computador para encapsular comportamentos variáveis para o mesmo objeto baseado no seu estado interno.

Uma implementação que não gosto…

Existem algumas maneiras de implementar um padrão de estado em Unidade. Um dos métodos comuns que eu vejo é feito usando a declaração de switch. Geralmente se parece com isto.

Problemas

Embora isto funcione tecnicamente, há alguns problemas sérios que irão surgir com o tempo.

Primeiro, toda a nossa lógica está agora dentro desta única classe. Atacar, defender, voltar para casa, e qualquer outro estado que queiramos criar, tudo precisa ser adicionado aqui. Isto levará a uma grande classe mestre inchada para a nossa IA. Isso também tornará nosso código mais provável de quebrar, já que estamos editando a mesma classe para cada tipo de lógica. Em pouco tempo, esta classe pode facilmente se transformar em um desastre de mais de 1000 linhas.

Não há também uma boa maneira de lidar com eventos que acontecem em um estado de entrada e saída. Muitas vezes, queremos que um personagem ou objeto faça algo quando entra num estado, repita uma ação enquanto está nesse estado, e faça algo totalmente diferente quando sai do estado.

Com essa configuração, teríamos que adicionar mais switches para entrar e sair do estado, inchando ainda mais a classe e tornando tudo mais difícil de entender.

Uma melhor implementação

Uma melhor abordagem é ter cada estado sendo sua própria classe, geralmente com uma classe ‘state’ base que herdaram de.

Desta forma, o caracter ou objeto só precisa ter uma referência ao seu estado atual e uma chamada para fazer a atualização desse estado. Quando queremos adicionar um novo estado, nós apenas criamos uma nova classe.

Usando este método, nossa classe de caracteres parece um pouco diferente:

Você pode notar que as classes são mais ou menos do mesmo tamanho… mas isso é apenas porque a primeira versão não faz nada (principalmente para mantê-la digerível). Se essa primeira classe estivesse fazendo tudo o que o sistema que vou mostrar, seria enorme.

Como funciona?

Se você olhar para o método Update, você verá que ele simplesmente chama o método Tick() no estado atual. Com essa chamada, o estado controla o que o caractere fará em cada frame.

Temos também um método SetState que nos permite passar em um estado que queremos que o caractere entre. Chamando SetState e passando em diferentes estados, podemos mudar completamente o comportamento deste caractere, sem alterar nada na própria classe de caracteres.

Dependente da sua configuração, pode ser aconselhável manter os estados e alternar entre eles para evitar a coleta de lixo. Para manter isso fácil de entender, eu pulei isso, mas vale a pena ter em mente se suas mudanças de estado são rápidas e você está em uma plataforma onde isso importa.

Qual é a classe State?

A classe state é uma classe base abstrata que vamos usar para todos os estados que criarmos. Ela define um contrato muito básico para o que um estado deve implementar e outras coisas que ele pode opcionalmente implementar.

A referência de caracteres também é adicionada a esta classe base de estados, porque todos os nossos estados neste exemplo irão interagir com um caracter de alguma forma. Nós pegamos o caractere no construtor da nossa classe state e o mantemos em uma variável protegida para que ele esteja disponível para nossas classes state.

Tambem temos o método abstrato Tick(). Torná-lo abstrato significa que nossas classes de estado são forçadas a implementá-lo.

Em contraste, nós temos dois métodos virtuais para OnStateEnter() & OnStateExit() que são opcionais. Por serem virtuais, eles têm uma implementação padrão que não faz nada, e não precisam ser implementados.

É a totalidade da classe state… bem simples direito?

Criar um estado – Retornar para casa

Agora temos o básico, vamos criar uma implementação de um estado que possamos conectar ao nosso personagem.

O retorno para casa simplesmente faz com que o personagem retorne para a sua localização de casa. Como não estamos realmente definindo o destino para home, é o Vector3.zero.

Tick

O método Tick diz ao caractere para MoveToward o destino (0, 0, 0).

O método MoveToward está em uma versão do caractere no final deste post. Em um projeto real, este movimento provavelmente seria controlado por outro componente como uma classe ‘CharacterMovement’ ou algo mais.

Então ele verifica se o caractere chegou em casa, e se sim ele diz ao caractere para mudar para um estado ‘vagabundo’. Note que nós passamos no caractere ao criar o novo estado. Isto porque nosso estado é projetado para requerer um caractere.

EnterState

Nós também mudamos a cor do caractere quando o estado de retorno para casa é inserido via OnStateEnter. Então como um caractere está indo para casa, ele é azul.

Criando outro estado – Wander

Para que uma máquina de estados faça sentido, nós precisamos de mais de um estado. De fato, nosso estado de volta para casa realmente quer mudar o caractere para um estado de vagabundagem uma vez feito, então precisamos criar esse estado de vagabundagem.

O estado de vagabundagem irá escolher um local aleatório e mover o caractere para essa posição. Quando ele atingir essa posição, ele irá escolher outro ponto para vagar para.

Ele continuará fazendo isso até que ele seja vagado por 5 segundos (baseado em nossa variável wanderTime). Nesse ponto, ele dirá ao personagem para voltar a um estado ReturnHome (linha 43).

O estado de vagar também muda a cor do nosso personagem para verde através do método OnStateEnter.

Vejamos como é

Aqui, criei uma cena com uma dúzia de ‘personagens’ representados como cubos.

Vais vê-los a vaguear por aí enquanto estão verdes, depois vais para casa novamente quando estiverem azuis, e repetes o processo.

O Personagem Completo

Comencionei acima que a classe de personagens completos está aqui com o método MoveToward. Novamente, em um projeto real, eu recomendaria manter seu movimento básico separado em outro componente, mas para manter isto simples, aqui está na classe Character.

Conclusions

O padrão de estado é incrivelmente poderoso. Você pode construir sistemas de IA, menus, e mais com facilidade uma vez que você se sinta confortável com este padrão.

Esta máquina de estados também é muito básica e pode ser estendida para fazer coisas como estados de cache (para evitar novas alocações de memória) ou personalizada mais para suas necessidades.

Outra ótima maneira de construir máquinas de estados é através de scriptableobjects. Uma configuração de máquina de estados usando scriptableobjects pode ser facilmente configurada no editor/inspetor por designers sem nunca tocar no código, mas esse é um assunto um pouco mais complicado que eu posso mergulhar mais tarde.

Na próxima vez que você precisar construir um pouco de lógica de jogo, tente configurar uma máquina de estados própria (ou copiar esta como um ponto de partida), tenho certeza que vai ajudar e você vai gostar do novo padrão.

Deixe uma resposta

O seu endereço de email não será publicado.