post

Las máquinas de estados son útiles para un buen montón de cosas distintas, pero usualmente se usan en videojuegos para crear inteligencias artificiales sencillas y sin muchas complicaciones (en caso de algo más complejo, recomendamos recurrir a árboles de comportamiento). En este artículo veremos cómo crear una estructura genérica y concretaremos su uso en una implementación sencilla pero suficiente para demostrar su uso.

El funcionamiento básico

La idea consiste en hacer, como decía, una máquina de estados muy sencilla. Para eso usaremos el mismo sistema de componentes y crearemos dos estructuras bien diferenciadas: los distintos estados y la máquina de estados, que los controlará. Las dos fases más representativas, la del primer frame y en transición de un estado a otro, se explican a continuación.

primer-frame

Al comenzar la ejecución, la máquina de estados habilita el primer estado (que en Unity estará representado con un script). Con ello ejecuta el código existente en OnEnable y Update tal y como estamos acostumbrados. Antes de finalizar el frame, también comprobamos las condiciones de salida. Tal y como representa la flecha roja de la imagen, en esta ocasión no hay un cambio de estado. Por tanto, en el siguiente frame la ejecución volverá a iterar el código existente en Update.

cambio-de-estado

Esta vez, como vemos, la condición de salida sí se ha dado. Por tanto, la máquina de estados se encarga de deshabilitar el estado actual. Con ello ejecuta lo existente en OnDisable en caso de que hubiera algo. A continuación habilita el nuevo estado y, como hemos visto antes, se ejecuta OnEnable y luego Update.

 

Las clases que nos harán falta

Para la construcción de este sistema vamos a escribir una clase llamada “StateMachine” que nos servirá para todas las máquinas que queramos crear. Los estados heredarán de otra clase que escribiremos, “State”, que a su vez heredará de MonoBehaviour.

Remarco especialmente el uso de “InvokeRepeating”. Su función es llamar desde el segundo 0 (segundo parámetro) al método “Check” (primer parámetro) cada “checkExitRate” segundos (tercer parámetro), variable que hemos hecho pública para poder especificarla desde el inspector. Así, las condiciones de salida no se comprueban a cada frame dado que normalmente no es necesario. En otra variable pública, “initialState”, colocaremos en el inspector el estado inicial de la máquina.

“ChangeState” será llamada desde el estado actual cuando una condición de salida haya sido positiva. El parámetro que recibe es el nuevo estado de la máquina. Para cambiar de uno a otro, primero deshabilitaremos el actual, asignaremos el nuevo estado como actual y lo habilitaremos.

Es importante recordar cancelar la invocación del método “Check” en caso de que el componente o el gameObject en el que esté sea destruido. En caso contrario, Unity daría un error de ejecución.

Sencillísimo script. Lo primero que hacemos es asignar a la variable “stateMachine” la referencia de la máquina de estados del gameObject. También es importante remarcar que el modificador de acceso es protected y no private para que las clases que hereden tengan también acceso a esta variable.

“CheckExit” será usada para comprobar las condiciones de salida. Es necesario especificarla para que sea reconocida por StateMachine, aunque será en la implementación de cada estado donde usaremos override (en lugar de virtual) para sustituir esta implementación vacía tal y como luego veremos.

Como detalle, dado que tampoco es necesario, hemos especificado encima de la declaración de la clase que para funcionar se requiere el componente “StateMachine” en el gameObject donde queramos añadir este script.

 

Usando la máquina de estados

Ahora vamos con un caso específico también muy sencillo pero suficientemente ilustrativo.

maquina-de-estados

Como indica la imagen, comenzaremos en el estado “Follow” en el que se perseguirá al player (que habrá que asignar a través del inspector). Cuando esté a menos de 3 metros, cambiará al estado “Alert”, en el que parpadeará al color especificado en el inspector y tras unos segundos, volverá a “Follow”.

 

Mirando (algo) más allá

Basándonos en esta estructura lo podemos complicar tanto como lo requiera nuestro proyecto. En el desarrollo de Vaccine War los enemigos componen escuadrones, así que hemos ampliado la estructura con una entidad superior llamada “Squad” que sirve para organizarlos. Entre otras cosas nos sirve para establecer una comunicación entre ellos y, por ejemplo, avisar a los enemigos del escuadrón si alguno de sus miembros ha visto al jugador. Para un mejor rendimiento, también reparte las ejecuciones de las máquinas de estados entre distintos frames, distribuyéndolos según el número de soldados que componen las escuadras.

DESCARGAR EL PAQUETE CON LAS CLASES Y ESCENA DE EJEMPLO

Espero que el artículo haya sido de utilidad. Como siempre, abajo queda la caja de comentarios a la espera de dudas, mejoras o propuestas de nuevos artículos. Finalmente os animamos a echar un vistazo a los otros artículos de la asociación.

Acerca de NachoDA

Desarrollo videojuegos en Games for Tutti. Estudio Ing. Informática y diseño de juego, cuyo aprendizaje trato de reflejar en juegos de mesa y otros videojuegos.

6 respuestas sobre “Unity: Desarrollo de una sencilla máquina de estados

  1. ¡Estupendo artículo! Sin duda un modo útil y bien organizado de implementar comportamientos en nuestros juegos… Y yo ya sé cómo lo voy a usar dentro de mi proyecto 🙂 ¡Gracias por el aporte Nacho!

  2. Muy bueno, se entiende perfectamente y se agradece el paquete de descarga de ejemplo donde se puede ver lo simple que es funcionando en unity. 🙂

  3. Hermoso ejemplo
    Veo que es una forma ordenada de escribir codigo, no de la forma espaguetti que normalmente uso.
    Pienso usarlo en programar el comportamiento de un “enemigo” solo que ahora cada estado del enemigo estara aislado de los otros (aunque interactua con ellos) y facilitara la legibilidad y lo que mas me gusta, que podre reusar ciertos “estados”.

    Gracias

  4. Otra cosa, me parece que CheckExit() puede escoger entre varios estados, y no como en el ejemplo.
    Ejemplo si check exit encuentra enemigo puede. A: Huir o B: Atacar… etc.
    Como dice el autor, este es un excelente punto de inicio para continuar.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *