En el proceso de programación de entidades en el juego, surgen situaciones en las que deben actuar en diferentes condiciones de diferentes maneras, lo que sugiere el uso de 
estados .
Pero si decide usar la fuerza bruta, el código se convertirá rápidamente en un caos enredado con muchas declaraciones if-else anidadas.
Para una solución elegante a este problema, puede usar el patrón de diseño de estado. ¡Le dedicaremos este tutorial!
Del tutorial usted:
- Aprenda los conceptos básicos de la plantilla de estado en Unity.
- Aprenderá qué es una máquina de estado y cuándo usarla.
- Aprende a usar estos conceptos para controlar el movimiento de tu personaje.
Nota : este tutorial es para usuarios avanzados; se asume que ya sabe cómo trabajar en Unity y tiene un nivel promedio de conocimiento de C #. Además, este tutorial utiliza Unity 2019.2 y C # 7.
Llegar al trabajo
Descargar 
materiales del proyecto . Descomprima el 
archivo zip y abra el proyecto de 
inicio en Unity.
Hay varias carpetas en el proyecto que lo ayudarán a comenzar. La carpeta 
Assets / RW contiene las carpetas 
Animaciones , 
Materiales , 
Modelos , 
Prefabs , 
Recursos , 
Escenas , 
Scripts y 
Sonidos , nombrados de acuerdo con los recursos que contienen.
Para completar el tutorial, trabajaremos solo con 
escenas y 
guiones .
Vaya a 
RW / Scenes y abra 
Main . En el modo Juego, verás un personaje en una capucha dentro de un castillo medieval.
Haga clic en 
Reproducir y observe cómo se mueve la 
cámara para ajustarse al marco del 
personaje . Por el momento, en nuestro pequeño juego no hay interacciones, trabajaremos en ellas en el tutorial.
Explora el personaje
En la 
jerarquía, seleccione 
Carácter . Echa un vistazo al 
inspector . Verá un 
componente con el mismo nombre que contiene la lógica de control de 
caracteres .
Abra 
Character.cs ubicado en 
RW / Scripts .
El script realiza muchas acciones, pero la mayoría de ellas no son importantes para nosotros. Por ahora, prestemos atención a los siguientes métodos.
- Move: mueve el personaje, recibiendo valores de tipo- speedflotación como la velocidad de movimiento y- rotationSpeedVelocidad como la velocidad angular.
- ResetMoveParams: este método restablece los parámetros utilizados para animar el movimiento y la velocidad angular del personaje. Se usa solo para limpiar.
- SetAnimationBool: establece el parámetro de animación- paramde tipo Bool en valor.
- CheckCollisionOverlap: recibe un- Vector3y devuelve un- boolque determina si hay colisionadores dentro del radio especificado desde el- TriggerAnimation:- TriggerAnimationel parámetro de animación del parámetro de entrada.
- ApplyImpulse:- ApplyImpulsepulso al carácter igual a la- forceparámetro de entrada- forcetipo- Vector3.
A continuación verá estos métodos. En nuestro tutorial, sus contenidos y trabajo interno no son importantes.
¿Qué son las máquinas de estado?
Una máquina de estados es un concepto en el que un contenedor almacena el estado de algo en un momento dado en el tiempo. Basado en los datos de entrada, puede proporcionar una conclusión dependiendo del estado actual, pasando este proceso a un nuevo estado. Las máquinas de estado se pueden representar como un 
diagrama de estado . La preparación de un diagrama de estado le permite pensar en todos los estados posibles del sistema y las transiciones entre ellos.
Máquinas de estado
Las máquinas de estado finito o 
FSM (máquina de estado finito) es una de las cuatro familias principales de 
máquinas . Los autómatas son modelos abstractos de máquinas simples. Se estudian en el marco de la 
teoría de los autómatas , la rama teórica de la informática.
En pocas palabras:
- FSM consiste en una cantidad finita de condición . En un momento dado , solo uno de estos estados está activo .
- Cada estado determina en qué estado entrará como salida en función de la secuencia recibida de información entrante .
- El estado de salida se convierte en el nuevo estado activo. En otras palabras, hay una transición entre estados .
Para comprender mejor esto, considere el carácter de un juego de plataformas que está en el terreno. El personaje está en el estado de 
pie . Este será su 
estado activo hasta que el jugador presione el botón para que el personaje salte.
El estado 
Permanente identifica una pulsación de botón como una 
entrada importante y, como 
salida , cambia al estado de 
Salto .
Supongamos que hay un cierto número de tales estados de movimiento y un personaje solo puede estar en uno de los estados a la vez. Este es un ejemplo de FSM.
Máquinas de estado jerárquico
Considere un juego de plataformas con FSM, en el que varios estados comparten una lógica física común. Por ejemplo, puede moverse y saltar en los estados 
agachado y de 
pie . En este caso, varias variables entrantes conducen al mismo comportamiento y salida de información para dos estados diferentes.
En tal situación, sería lógico delegar el comportamiento general a algún otro estado. Afortunadamente, esto se puede lograr utilizando máquinas de estado 
jerárquicas .
En un FSM jerárquico, hay 
subestados que 
delegan información entrante sin 
procesar a sus 
subestados . Esto a su vez le permite reducir con gracia el tamaño y la complejidad de la FSM, manteniendo su lógica.
Plantilla de estado
En su libro 
Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson y John Vlissidis ( 
The Gang of Four ) definieron la 
tarea de la plantilla del Estado de la siguiente manera:
“Debe permitir que el objeto cambie su comportamiento cuando cambia su estado interno. En este caso, parecerá que el objeto ha cambiado su clase ".
Para comprender mejor esto, considere el siguiente ejemplo:
- Un script que recibe información entrante para la lógica del movimiento se adjunta a una entidad en el juego.
- Esta clase almacena una variable de estado actual que simplemente se refiere a una instancia de la clase de estado .
- La información entrante se delega a este estado actual, que la procesa y crea un comportamiento definido dentro de sí mismo. También maneja las transiciones de estado requeridas.
Por lo tanto, debido al hecho de que en diferentes momentos la variable de 
estado actual se refiere a diferentes estados, parecerá que la misma clase de script se comporta de manera diferente. Esta es la esencia de la plantilla de "Estado".
En nuestro proyecto, la clase de 
personaje antes mencionada se comportará de manera diferente dependiendo de los diferentes estados. ¡Pero necesitamos que se comporte!
En el caso general, hay tres puntos clave para cada clase de estado que permiten el comportamiento del estado en su conjunto:
- Entrada : este es el momento en que una entidad ingresa a un estado y realiza acciones que deben realizarse solo una vez al ingresar al estado.
- Salida : similar a la entrada: todas las operaciones de reinicio se realizan aquí, que deben realizarse solo antes de que cambie el estado.
- Update Loop : Aquí está la lógica básica de actualización que se ejecuta en cada cuadro. Se puede dividir en varias partes, por ejemplo, un ciclo para actualizar la física y un ciclo para procesar la entrada del jugador.
Definir un estado y una máquina de estados
Vaya a 
RW / Scripts y abra 
StateMachine.cs .
La máquina de estado , como puede suponer, proporciona una abstracción para la máquina de estado. Tenga en cuenta que 
CurrentState ubicado correctamente dentro de esta clase. Almacenará un enlace al estado actual activo de la máquina de estado.
Ahora, para definir el concepto del 
estado , vayamos a 
RW / Scripts y abramos el script 
State.cs en el IDE.
El estado es una clase abstracta que usaremos como 
modelo del cual se derivan todas las 
clases de estados del proyecto. Parte del código en los materiales del proyecto ya está listo.
DisplayOnUI solo muestra el nombre del estado actual en la IU en pantalla. No es necesario que conozca su estructura interna, solo comprenda que recibe un enumerador de tipo 
UIManager.Alignment como parámetro de entrada, que puede ser 
Left o 
Right . La visualización del nombre del estado en la parte inferior izquierda o derecha de la pantalla depende de ello.
Además, hay dos variables protegidas, 
character y 
stateMachine . La variable de 
character refiere a una instancia de la clase 
Character , y 
stateMachine refiere a una instancia 
de la máquina de estados asociada con el estado.
Al crear una instancia de estado, el constructor enlaza 
character y 
stateMachine .
Cada una de las muchas instancias de 
Character en una escena puede tener su propio conjunto de estados y máquinas de estados.
Ahora agregue los siguientes métodos a 
State.cs y guarde el archivo:
 public virtual void Enter() { DisplayOnUI(UIManager.Alignment.Left); } public virtual void HandleInput() { } public virtual void LogicUpdate() { } public virtual void PhysicsUpdate() { } public virtual void Exit() { } 
Estos métodos virtuales definen los puntos de estado clave descritos anteriormente. Cuando 
la máquina de estados realiza una transición entre estados, llamamos a 
Exit para el estado anterior e 
Enter nuevo 
estado activo .
HandleInput , 
LogicUpdate y 
PhysicsUpdate juntos definen 
un ciclo de actualización . 
HandleInput maneja la entrada del jugador. 
LogicUpdate procesa lógica básica, mientras que 
PhyiscsUpdate procesa 
PhyiscsUpdate lógica y física.
Ahora abra 
StateMachine.cs nuevamente, agregue los siguientes métodos y guarde el archivo:
 public void Initialize(State startingState) { CurrentState = startingState; startingState.Enter(); } public void ChangeState(State newState) { CurrentState.Exit(); CurrentState = newState; newState.Enter(); } 
Initialize configura la máquina de estado estableciendo 
CurrentState en 
startingState y llamando a 
Enter para ello. Esto inicializa la máquina de estado, por primera vez configurando el estado activo.
ChangeState maneja 
las transiciones de 
estado . Llama a 
Exit para el 
CurrentState anterior antes de reemplazar su referencia con 
newState . Al final, llama a 
Enter para 
newState .
Por lo tanto, configuramos el 
estado y 
la máquina de estados .
Crear estados de movimiento
Echa un vistazo al siguiente diagrama de estado, que muestra los diferentes 
estados de movimiento de la esencia 
del jugador en el juego. En esta sección, implementamos la plantilla "Estado" para el 
movimiento que se muestra en la figura 
FSM :
Preste atención a los estados de movimiento, es decir, de 
pie , 
agachándose y 
saltando , así como a cómo los datos entrantes provocan transiciones entre los estados. Este es un FSM jerárquico en el que 
Grounded es un 
subestado para los 
subestados Ducking y 
Standing .
Regrese a Unity y vaya a 
RW / Scripts / States . Allí encontrará varios archivos C # con nombres que terminan en 
Estado .
Cada uno de estos archivos define una clase, cada una de las cuales se hereda de 
State . Por lo tanto, estas clases definen los estados que utilizaremos en el proyecto.
Ahora abra 
Character.cs desde la carpeta 
RW / Scripts .
Desplácese sobre el archivo 
#region Variables y agregue el siguiente código:
 public StateMachine movementSM; public StandingState standing; public DuckingState ducking; public JumpingState jumping; 
Este 
movementSM refiere a una máquina de estados que procesa la lógica de movimiento para la instancia de 
Character . También agregamos enlaces a tres estados que implementamos para cada tipo de movimiento.
Vaya a 
#region MonoBehaviour Callbacks en el mismo archivo. Agregue los siguientes métodos 
MonoBehaviour y luego guarde
 private void Start() { movementSM = new StateMachine(); standing = new StandingState(this, movementSM); ducking = new DuckingState(this, movementSM); jumping = new JumpingState(this, movementSM); movementSM.Initialize(standing); } private void Update() { movementSM.CurrentState.HandleInput(); movementSM.CurrentState.LogicUpdate(); } private void FixedUpdate() { movementSM.CurrentState.PhysicsUpdate(); } 
- En Startcódigo crea una instancia de la Máquina de estado y la asigna amovementSM, y también crea una instancia de varios estados de movimiento. Al crear cada uno de los estados de movimiento, pasamos referencias a la instancia deCharacterutilizando lathis, así como la instancia de motionSM. Al final, llamamosInitializeparamovementSMy pasamosStandingcomo estado inicial.
- En el método de Update, llamamos aHandleInputyLogicUpdateparaLogicUpdatede la máquinamovementSM. Del mismo modo, enFixedUpdatellamamos aPhysicsUpdatepara elCurrentStatede la máquinamovementSM. En esencia, esto delega tareas a un estado activo; Este es el significado de la plantilla "Estado".
Ahora necesitamos establecer el comportamiento dentro de cada uno de los estados de movimiento. Prepárate, ¡habrá mucho código!
Firmeza permanente
Regrese a 
RW / Scripts / States en la ventana Proyecto.
Abra 
Grounded.cs y observe que esta clase tiene un constructor que coincide con el constructor 
State . Esto es lógico porque esta clase hereda de ella. Verá lo mismo en todas las demás clases de 
estado .
Agregue el siguiente código:
 public override void Enter() { base.Enter(); horizontalInput = verticalInput = 0.0f; } public override void Exit() { base.Exit(); character.ResetMoveParams(); } public override void HandleInput() { base.HandleInput(); verticalInput = Input.GetAxis("Vertical"); horizontalInput = Input.GetAxis("Horizontal"); } public override void PhysicsUpdate() { base.PhysicsUpdate(); character.Move(verticalInput * speed, horizontalInput * rotationSpeed); } 
Esto es lo que pasa aquí:
- Redefinimos uno de los métodos virtuales definidos en la clase padre. Para preservar toda la funcionalidad que puede existir en el padre, llamamos al método basecon el mismo nombre de cada método anulado. Esta es una plantilla importante que continuaremos usando.
- La siguiente línea, EnterestablecehorizontalInputyverticalInputsus valores predeterminados.
- Dentro de Exitnosotros, como se mencionó anteriormente, llamamos al métodoResetMoveParams- En el método HandleInput, las variableshorizontalInputyverticalInputHandleInputvalores de los ejes de entrada horizontal y vertical. Gracias a esto, el jugador puede controlar al personaje usando las teclas W , A , S y D.
- En PhysicsUpdaterealizamos una llamadaMove, pasando las variableshorizontalInputyverticalInputmultiplicadas por las velocidades correspondientes. En laspeedvariablespeedla velocidad de movimiento se almacena, y en velocidad derotationSpeed, la velocidad angular.
Ahora abra 
Standing.cs y preste atención al hecho de que hereda de 
Grounded . Sucedió porque, como dijimos anteriormente, 
Standing es un subestado para 
Grounded . Hay diferentes formas de implementar esta relación, pero en este tutorial usamos la herencia.
Agregue los siguientes métodos de 
override y guarde el script:
 public override void Enter() { base.Enter(); speed = character.MovementSpeed; rotationSpeed = character.RotationSpeed; crouch = false; jump = false; } public override void HandleInput() { base.HandleInput(); crouch = Input.GetButtonDown("Fire3"); jump = Input.GetButtonDown("Jump"); } public override void LogicUpdate() { base.LogicUpdate(); if (crouch) { stateMachine.ChangeState(character.ducking); } else if (jump) { stateMachine.ChangeState(character.jumping); } } 
- En Enterconfiguramos las variables heredadas deGrounded. Aplica laspeedMovementSpeedyRotationSpeedspeedRotationSpeedpersonaje a laspeedyspeedrotationSpeed. Luego se relacionan, respectivamente, con la velocidad normal de movimiento y la velocidad angular destinada a la esencia del personaje.
 
 Además, las variables para almacenar la entrada decrouchyjumpse restablecen a falso.
- Dentro de HandleInput, las variablescrouchyjumpalmacenan la entrada del jugador para las sentadillas y saltos. Si en la escena Principal el jugador presiona la tecla Mayús, la posición en cuclillas se establece en verdadera. Del mismo modo, un jugador puede usar la tecla Espacio parajump.
- En LogicUpdateverificamos las variablesLogicUpdateyjumpde tipobool. Sicrouches verdadero, entoncesmovementSM.CurrentStatecambia acharacter.ducking. Sijumpes verdadero, el estado cambia acharacter.jumping.
Guarde y ensamble el proyecto, luego haga clic en 
Reproducir . Puede moverse por la escena con las teclas 
W , 
A , 
S y 
D. Si intenta presionar 
Mayús o 
Barra espaciadora , se producirá un comportamiento inesperado, porque los estados correspondientes aún no están implementados.
Intenta moverte debajo de los objetos de la mesa. Verás que, debido a la altura del colisionador del personaje, esto no es posible. Para que el personaje haga esto, debes agregar un comportamiento en cuclillas.
Subimos debajo de la mesa
Abra el script 
Ducking.cs . Tenga en cuenta que 
Ducking también hereda de la clase 
Grounded por los mismos motivos que 
Standing . Agregue los siguientes métodos de 
override y guarde el script:
 public override void Enter() { base.Enter(); character.SetAnimationBool(character.crouchParam, true); speed = character.CrouchSpeed; rotationSpeed = character.CrouchRotationSpeed; character.ColliderSize = character.CrouchColliderHeight; belowCeiling = false; } public override void Exit() { base.Exit(); character.SetAnimationBool(character.crouchParam, false); character.ColliderSize = character.NormalColliderHeight; } public override void HandleInput() { base.HandleInput(); crouchHeld = Input.GetButton("Fire3"); } public override void LogicUpdate() { base.LogicUpdate(); if (!(crouchHeld || belowCeiling)) { stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); belowCeiling = character.CheckCollisionOverlap(character.transform.position + Vector3.up * character.NormalColliderHeight); } 
- Dentro de Enterparámetro que provoca que la animación de sentadilla se active se pone en cuclillas, lo que permite la animación de sentadilla. Las propiedadescharacter.CrouchSpeedycharacter.CrouchRotationSpeedtienen asignados los valores despeedyrotation, que devuelven el movimiento del personaje y la velocidad angular cuando se mueve en cuclillas .
 
 Siguientecharacter.CrouchColliderHeightCrouchColliderHeight establece el tamaño del colisionador del personaje, que devuelve la altura deseada del colisionador cuando se pone en cuclillas. Al final,belowCeilingrestablece a falso.
- Dentro de Exitel parámetro de animación en cuclillas se establece en falso. Esto deshabilita la animación en cuclillas. Luego se establece la altura normal del colisionador, devuelta porcharacter.NormalColliderHeight.
- Dentro de HandleInputvariablecrouchHeldestablece el valor de entrada del reproductor. En la escena Principal , mantener Shift establececrouchHelden verdadero.
- Dentro de PhysicsUpdatevariablebelowCeilingasigna un valor al pasar un punto en formatoVector3con la cabeza del objeto del juego del personaje al métodoCheckCollisionOverlap. Si hay una colisión cerca de este punto, esto significa que el personaje está bajo algún tipo de techo.
- Internamente, LogicUpdatecomprueba sicrouchHeldobelowCeilinges verdadero. Si ninguno de ellos es cierto, entoncesmovementSM.CurrentStatecambia acharacter.standing.
Construye el proyecto y haz clic en 
Reproducir . Ahora puedes moverte por la escena. Si presionas 
Shift , el personaje se sentará y podrás moverte en la sentadilla.
También puedes subir debajo de la plataforma. Si sueltas 
Shift mientras estás debajo de las plataformas, el personaje seguirá en cuclillas hasta que abandone su refugio.
¡Alzate!
Open 
Jumping.cs . Verá un método llamado 
Jump . No te preocupes por cómo funciona; es suficiente entender que se usa para que el personaje pueda saltar teniendo en cuenta la física y la animación.
Ahora agregue los métodos de 
override habituales y guarde el script
 public override void Enter() { base.Enter(); SoundManager.Instance.PlaySound(SoundManager.Instance.jumpSounds); grounded = false; Jump(); } public override void LogicUpdate() { base.LogicUpdate(); if (grounded) { character.TriggerAnimation(landParam); SoundManager.Instance.PlaySound(SoundManager.Instance.landing); stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); grounded = character.CheckCollisionOverlap(character.transform.position); } 
- Dentro de EnterSingletonSoundManagerreproduce el sonido del salto. Luego agroundedrestablece a su valor predeterminado. Al final, se llamaJump.
- Dentro de PhysicsUpdatepuntoPhysicsUpdateal lado de las piernas del personaje se envía aCheckCollisionOverlap, lo que significa que cuando el personaje está en el suelo,groundedse establecerá en verdadero.
- En LogicUpdate, si lagroundedes verdadera, llamamos aTriggerAnimationpara habilitar la animación de la toma de contacto, se reproduce el sonido de la toma de contacto y elmovementSM.CurrentStatecambia acharacter.standing.
Entonces, en esto hemos completado la implementación completa del desplazamiento FSM utilizando 
la plantilla "Estado" . Construye el proyecto y ejecútalo. Presiona 
la barra 
espaciadora para hacer que el personaje salte.
¿A dónde ir después?
Los 
materiales del proyecto tienen un proyecto borrador y un proyecto terminado.
A pesar de su utilidad, las máquinas de estado tienen limitaciones. Las máquinas de estado concurrente y las máquinas Pushdown Automaton pueden manejar algunas de estas limitaciones. Puedes leer sobre ellos en el libro de Robert Nystrom 
Game Programming Patterns .
Además, el tema se puede explorar más profundamente al examinar los 
árboles de comportamiento utilizados para crear entidades más complejas en el juego.