Patrones de diseño de Unity3D – Estado – Construcción de una máquina de estado de Unity3D

  • Inicio /
  • Unity3D
  • / Patrones de diseño de Unity3D – Estado – Construcción de una máquina de estado de Unity3D
Por Jason Weimann /26 de mayo, 2017

Los patrones de diseño son soluciones reutilizables para problemas comunes encontrados en proyectos de software. El uso de buenos patrones de diseño en Unity puede ayudar a mantener su proyecto limpio, mantenible y fácil de extender. También hace que sean más fáciles de construir.. Hoy, voy a cubrir el patrón de estado y los fundamentos de la construcción de una máquina de estado en Unity3D.

El patrón de estado es un patrón de diseño de software de comportamiento que implementa una máquina de estado de una manera orientada a objetos. … Este patrón se utiliza en la programación de computadoras para encapsular un comportamiento variable para el mismo objeto basado en su estado interno.

Una implementación que no me gusta..

Hay algunas maneras de implementar un patrón de estado en Unity. Uno de los métodos comunes que veo se hace mediante el uso de la sentencia switch. Generalmente se ve algo como esto.

Problemas

Aunque esto técnicamente funciona, hay algunos problemas graves que surgirán con el tiempo.

Primero, toda nuestra lógica está ahora dentro de esta única clase. Atacar, defender, volver a casa, y cualquier otro estado que queramos crear, todo tiene que ser añadido aquí. Esto conducirá a una gran clase maestra hinchada para nuestra IA. También hará que nuestro código sea más propenso a romperse ya que estamos editando la misma clase para cada tipo de lógica. En poco tiempo, esta clase podría convertirse fácilmente en un desastre de más de 1000 líneas.

Tampoco hay una buena manera de tratar los eventos que ocurren al entrar y salir de un estado. Muy a menudo, queremos que un personaje u objeto haga algo cuando entra en un estado, repetir una acción mientras está en ese estado, y hacer algo totalmente diferente cuando sale del estado.

Con esta configuración, tendríamos que añadir más interruptores para entrar y salir del estado, hinchando aún más la clase y haciendo todo más difícil de entender.

Una mejor implementación

Una mejor aproximación es hacer que cada estado sea su propia clase, generalmente con una clase base ‘state’ de la que heredan.

De esta manera, el personaje u objeto sólo necesita tener una referencia a su estado actual y una llamada para hacer esa actualización de estado. Cuando queremos añadir un nuevo estado, simplemente creamos una nueva clase.

Usando este método, nuestra clase de personaje se ve un poco diferente:

Puedes notar que las clases son más o menos del mismo tamaño.. pero eso es sólo porque la primera versión no hace realmente nada (principalmente para mantenerlo digerible). Si esa primera clase hiciera todo lo que hace el sistema que te voy a mostrar, sería enorme.

¿Cómo funciona?

Si miras el método Update, verás que simplemente llama al método Tick() sobre el estado actual. Con esa llamada, el estado controla lo que el personaje hará en cada fotograma.

También tenemos un método SetState que nos permite pasar un estado en el que queremos que entre el personaje. Llamando a SetState y pasando diferentes estados, podemos cambiar completamente el comportamiento de este personaje, sin cambiar nada en la propia clase de personaje.

Dependiendo de tu configuración, puede ser aconsejable mantener los estados alrededor y cambiar entre ellos para evitar la recolección de basura. Para mantener esto fácil de entender, he omitido eso, pero vale la pena tenerlo en cuenta si tus cambios de estado son rápidos y estás en una plataforma donde importa.

¿Qué es la clase State?

La clase State es una clase base abstracta que usaremos para todos los estados que creemos. Define un contrato muy básico para lo que un estado debe implementar y otras cosas que puede implementar opcionalmente.

La referencia al carácter también se añade a esta clase base de estado, porque todos nuestros estados en este ejemplo se interconectarán con un carácter de alguna manera. Tomamos el carácter en el constructor de nuestra clase de estado y lo mantenemos en una variable protegida para que esté disponible para nuestras clases de estado.

También tenemos el método abstracto Tick(). Hacerlo abstracto significa que nuestras clases de estado están obligadas a implementarlo.

En cambio, tenemos dos métodos virtuales para OnStateEnter() & OnStateExit() que son opcionales. Debido a que son virtuales, tienen una implementación por defecto que no hace nada, y no están obligados a ser implementados.

Eso es la totalidad de la clase de estado… bastante simple ¿no?

Creación de un Estado – Volver a casa

Ahora que tenemos lo básico, vamos a crear una implementación de un estado que podemos enganchar a nuestro personaje.

El estado de volver a casa simplemente hace que el personaje vuelva a su ubicación de origen. Ya que no estamos definiendo el destino para home, es el Vector3.zero por defecto.

Tick

El método Tick le dice al personaje que se muevaHacia el destino (0, 0, 0).

El método MoveToward está en una versión del personaje al final de este post. En un proyecto real, este movimiento probablemente sería controlado por otro componente como una clase ‘CharacterMovement’ o algo más.

Entonces comprueba si el personaje llegó a casa, y si es así le dice al personaje que cambie a un estado ‘wander’. Fíjate que pasamos el personaje al crear el nuevo estado. Esto se debe a que nuestro estado está diseñado para requerir un personaje.

EnterState

También cambiamos el color del personaje cuando se entra en el estado de vuelta a casa a través de OnStateEnter. Así que como un personaje está volviendo a casa, es azul.

Creando otro Estado – Wander

Para que una máquina de estados tenga sentido, necesitamos más de un estado. De hecho, nuestro estado de regreso a casa en realidad quiere cambiar al personaje a un estado de vagabundeo una vez que haya terminado, por lo que necesitamos crear ese estado de vagabundeo.

El estado de vagabundeo elegirá una ubicación al azar y moverá al personaje hacia esa posición. Cuando llegue a esa posición, elegirá otro lugar para deambular.

Continuará haciendo esto hasta que haya deambulado durante 5 segundos (basado en nuestra variable wanderTime). En ese momento, le dirá al personaje que vuelva a un estado ReturnHome (línea 43).

El estado wander también cambia el color de nuestro personaje a verde a través del método OnStateEnter.

Veamos cómo se ve

Aquí, he creado una escena con una docena de los ‘personajes’ representados como cubos.

Verás que se pasean mientras están en verde, luego vuelven a su casa cuando están en azul, y repiten el proceso.

El personaje completo

Mencioné arriba que la clase de personaje completa está aquí con el método MoveToward. De nuevo, en un proyecto real, recomendaría mantener su movimiento básico separado en otro componente, pero para mantener esto simple, aquí está en la clase Personaje.

Conclusiones

El patrón de estado es increíblemente poderoso. Puedes construir sistemas de IA, menús y mucho más con facilidad una vez que te sientas cómodo con este patrón.

Esta máquina de estados es también muy básica y puede ser extendida para hacer cosas como almacenar estados en caché (para evitar nuevas asignaciones de memoria) o personalizarla más a tus necesidades.

Otra gran manera de construir máquinas de estados es a través de scriptableobjects. Una configuración de máquina de estado usando scriptableobjects puede ser fácilmente configurada en el editor/inspector por los diseñadores sin tocar nunca el código, pero eso es un tema un poco más complicado en el que puede que me sumerja más adelante.

La próxima vez que necesites construir un poco de lógica de juego, intenta configurar una máquina de estado propia (o copia esta como punto de partida), estoy seguro de que te ayudará y disfrutarás del nuevo patrón.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.