¡Buenas noches! O mañana. Hoy terminaremos de crear nuestra Máquina de Estados.. sí, quizás sea incluso más trabajo que en la primera parte.
Antes de continuar, quiero avisar que ya hay dos repositorios publicados: el primero fue, como dije en mi artículo pasado, la Máquina de Estados. Por último esta el proyecto de plataformas completo. Dejaré los respectivos enlaces:
- Máquina de Estados: https://github.com/CeesarLeon2002/state-machine-for-platformer-games.
- Juego de Plataformas: https://github.com/CeesarLeon2002/platformer-godot-game.
Oh, casi lo olvido: la primera parte de este tutorial se encuentra aquí.
Scripts importantes, y los estados de la máquina.
Vuelvan sobre su jugador. Es hora de que añadan un nodo llamado “Node”, y lo nombren “StateMachinePlayer” o algo por el estilo. El nodo “Node” es de las mejores opciones, ya que no utiliza otras funciones de nodos que puedan retrasar el rendimiento de nuestra Máquina de Estados.

El nodo principal de la máquina, tendrá como hijos directos otros nodos del mismo tipo, con el nombre de su respectivo estado. Por ejemplo: (Walk, Idle, Jump). Después les colocaremos un Script que se encargue de cada comportamiento, pero eso después.
Ahora que tenemos los nodos, los dejaremos ahí. Es necesario que hagamos un Script que se pueda usar en cualquier otro personaje que posea una Máquina de Estados. Un Script que sea la máquina de estados en sí. Con eso, simplemente tendremos que usar un “controlador” dentro de cada nodo que tenga la máquina de estados. Si no lo hacemos así, tenderemos a escribir más código del necesario, al momento de añadir una Máquina de Estados a cualquier otro personaje del mismo proyecto.
Ese Script lo crearemos de la siguiente forma:


Les mostraré lo que deben colocar dentro, y explicaré cada parte:

El diccionario “states_map” se encarga de guardar la dirección de todos los nodos que sean “estados” de la máquina. Ahora la dejamos vacía, pero en el “controlador” que se encuentra dentro de cada personaje, cuando llegue el momento, le daremos un valor. Por otro lado, “current_state” recibe el estado actual, y la “_active” es para el encendido y apagado de la máquina.
Para las funciones sí enumeraré una lista, vamos por ello:
- _ready(): Esta es muy simple. Todos los estados de la máquina van a disponer de una señal llamada “finished”. Es necesario que estén conectados a nuestra función de “change_state”, para que cada vez que termine un estado (“finished”) la máquina reciba la llamada y cambie al nuevo estado. Nota: los “hijos” son los nodos que creamos arriba, pronto les asignaremos un script con la señal “finished”.
- _input(event) and _physics_process(delta): Llegando a esta parte se darán cuenta de que usamos “current_state”. Sepan que esa variable guarda un nodo (cuando no es null). Ese nodo es uno de los estados, y todos los estados cuentan con una función llamada “handle_input(event)” y “update(delta)”. Sí… son un reemplazo de la función _input() y _ physics_process. No usamos las funciones “originales” dentro de los estados, porque crearía conflictos al manejar diferentes modos del parámetro “Event” y el tiempo “Delta”. Así como lo tenemos ahora, estamos usando un único valor que sale desde la Máquina de Estados.
- change_state(state_name): En esta función cambiamos los estados. Lo primero que se hace es comprobar que la máquina esta activa. Después, el estado actual se actualiza por la búsqueda que se realice en nuestro diccionario de mapas. Por ejemplo, sabiendo como funcionan los diccionarios en GDScript, para buscar el nodo “Idle”, tenemos que colocar la clave con la que guardamos ese estado. Esa clave es el “state_name” y cada vez que se emite la señal “finished”, se pasa un valor con el nombre del nuevo estado de la máquina. Tal que así: “emit_signal(‘finished’,”Idle”)”. Para finalizar, lo último es activar la función “enter” del estado actual. Seguro lo adivinas: “enter” es el reemplazo de los estados para “ready”.
- set_active: Otra papaya. Simplemente se encarga de activar o desactivar las funciones cuando llamemos a la función “set_active(true or false)”.
Perfecto, o casi perfecto. Puede que falten cosas por mejorar, pero ahí vamos aprendiendo. Por ahora nuestro código para la Máquina de Estados esta listo. Así como tenemos un script principal para la máquina, necesitamos uno para los estados. Cada estado independiente podrá heredar las funciones principales sin necesidad de repetir código.
Crearemos el script de la misma manera que el primero. Miren ahora, este es el código que tengo yo:

Muy corto. Primero creamos la señal “finished”. Recuerden que la invocamos en todos los estados, dentro del script de la máquina. Cuando usamos unos paréntesis para la señal, estaremos indicando que es necesario mandar un valor, cada vez que emitimos la señal. Nosotros generalmente haremos esto: “emit_signal(“finished”, “State_Idle”)”.
De resto, se darán cuenta que son las funciones que llamamos en el otro script. En el nodo principal no tienen importancia, sólo las colocamos para que estén creadas por defecto cada vez que añadamos un estado en nuestra máquina.
Controlador de la máquina dentro del jugador
Ya estamos terminando con nuestro trabajo. Vamos a crear el script del “controlador” de la máquina, en el nodo “StateMachinePlayer” que creamos al principio de este artículo. Aquí el código:

Fíjense que en donde dice “extends” he borrado la palabra “Node” y lo he reemplazado por la ruta que tiene el script de la Máquina de Estados principal dentro del proyecto. Eso hará que el código del otro script, forme parte del controlador, sin tener que escribirlo.
Ya que tenemos esto, pasemos a la función _ready(). Aprovechando que sólo se ejecuta una vez, pasaremos el nombre de cada estado (la clave), y su respectiva dirección dentro del jugador. Como son hijos del controlador, sólo hay que usar el signo “$” y escribir el nombre del estado, que generalmente es el mismo que la clave. Por último en esa función, usamos la función change_state(“Idle”) para decir que el primer estado en que se encontrará el jugador será el “Idle”.
Repetiremos una vez más la función change_state(state_name), y en su primera línea colocaremos un “.” y repetiremos el nombre. Con el “.” estamos accediendo a la función “change_state” que se encuentra arriba del script del controlador, osea en el script principal de la máquina, que se extiende sobre el controlador… haciendo eso, podremos procesar el estado con el código principal, que escribimos hace un rato. Nota: las últimas dos líneas no tienen importancia, son otra cosa.
Los estados independientes
Para esta parte necesitamos aplicar algo de lógica. Primero: el estado inicial es “Idle”. Por lo tanto, a partir de él deben salir otros estados: como el salto, el movimiento y el ataque. Sólo vamos a poder ejecutar un sólo estado a la vez. Un ejemplo sería que, cuando entremos en el estado del movimiento, sólo podremos salir cuando, dentro de su mismo código, demos instrucciones para que pasemos a otro estado, por ejemplo, el salto.
Empecemos pues, añadiendo el script del estado “Idle”:

¿Recuerdan cuando les dije que añadieran una animación para cada estado? Ya en la función _ready podemos decir (aunque esto es opcional y ustedes pueden hacer lo que quieran) que la animación “Idle” se reproduzca. Cuando usamos la palabra “owner”, estamos accediendo al nodo principal de la escena. En este caso el nodo principal es el KinematicBody2D del jugador, y uno de sus hijos, es el “Anim” que creamos en el artículo pasado.
Dentro de la función handle_input indicaremos que cuando se presione la flecha de arriba, compruebe que el “owner” osea el script del jugador, se encuentre en el piso. Si es así, puedes decir que el “estado a finalizado” y puedes “pasar al salto” (emit_signal(“finished”,”Jump”)). Desde ese momento, ya el script del Idle deja de ejecutarse, porque ahora el nuevo estado que estará ejecutándose será el salto.
Lo mismo pasaría si presionamos “Espace” y realizamos un ataque. Estaríamos cancelando el estado “Idle”, para pasar al “Attack”.
En la función update(delta) que se ejecuta en cada segundo, comprobaremos que la dirección del jugador no ha cambiado. De ser así significaría que queremos movernos, y emitimos la señal que llama al estado de movimiento.
Para los que quieran refrescar la memoria con respecto al código del “owner” o jugador, miren esta imagen del artículo pasado:

Esto, amigos míos, es todo lo que necesitan saber. Sólo falta que añadan el código de los otros estados y aprendan a salir de ellos para volver al “Idle”.
Continuemos, por ejemplo, con el código del “move_state”. Recuerden que casi todos los estados inician igual, y se extienden con la ruta del script de estados principal. Pasemos ahora al código que les quiero mostrar:

Todo igual ¿no?. Cuando el jugador tenga la dirección en 0 es porque ya esta quieto, y puede volver al estado “Idle”. Cuando saltas, cambias, etc. No veo complicaciones mayores. Igual si alguien tiene otra duda que no pueda resolver descargando el repositorio de la Máquina de Estados desde el enlace que dejé al inicio del capítulo, puede escribir un comentario. Con gusto lo ayudaré y actualizaré el artículo si es necesario.
Hola amigo, buen tutorial, pero me quedo la duda de porque y cuando agregaste los Text_State y el State… porfavor si puedes explicar bien eso de agradeceria… Muchas gracias!!
Epale, vi el comentario a la hora, pero era tardecito xD… Esos dos nodos son para mostrar cómo cambiaban los estados. El “Text_state” estaba por la cabeza del personaje, y el otro no recuerdo bien qué hacía.
Hola! He hecho todo lo que dijo el tutorial, pero algo no me funciona y no sé que es… el error dice “the function signature doesn’t match the parent. Parent signature is “Variant enter (variant)”. Si logras ayudarme se lo agradecería muchísimo!!
Hola amigo, ese error te lo manda el script State?
No, me manda al script Idle y Move
El error se debe a que en el Script Idle y Move copiaste mal el nombre de la función (creo que la función enter)… por ejemplo un enter(variable) cuando en el Script State esta creado enter() sin la variable entre paréntesis.
Ok, muchisimas gracias!!!
Hola, tengo un problema, me dice que no existe la funcion “handle input”, cuando yo ya la creé en el script estado y estoy seguro que tienen el mismo nombre
Es probable que no estés usando el extends “state.gd”, y en su lugar heredes directamente de “extends Node”
Porque no usas ready en vez de enter(), no entiendo porque hay 2 enter() uno que envia la señal finished y otro que tiene animacion para idle. Muchas gracias por el tutorial.
me sale un error en este codigo en el script de idle
owner.get_node(“anim”).play(“idle”)
attemp to call funcion ‘play’ in base ‘null instance’ on a null instance
Cuanad entro al enlace para descargar el código me sale “page not found” me podrías ayudar es que me gustaría ver el ejemplo porfavor
Entra a este repositorio amigo: https://gitlab.com/nautilus1/