El problema#
Un enemigo patrulla, persigue al jugador cuando lo ve, ataca de cerca y huye si le queda poca vida. La versión ingenua mete todo en Update:
// ❌ Crece sin control y se vuelve ilegible
void Update()
{
if (huyendo) { /* ... */ }
else if (viendoJugador && cerca) { /* atacar */ }
else if (viendoJugador) { /* perseguir */ }
else { /* patrullar */ }
// ...y cada rama con sus propios flags y condiciones cruzadas
}En cuanto hay varios estados con transiciones entre ellos, este bloque se convierte en un nido de if y booleanos imposible de mantener.
La solución: patrón State#
Una máquina de estados finita (FSM) separa cada comportamiento en su propia clase. En cada momento hay un estado activo, que sabe qué hacer y cuándo pasar a otro.
public interface IEstado
{
void Entrar(); // se ejecuta al entrar al estado
void Actualizar(); // cada frame, mientras es el estado activo
void Salir(); // se ejecuta al salir del estado
}El "motor" que gestiona el estado actual:
public class MaquinaEstados
{
private IEstado actual;
public void CambiarA(IEstado nuevo)
{
actual?.Salir();
actual = nuevo;
actual.Entrar();
}
public void Tick() => actual?.Actualizar();
}Un estado concreto#
Cada comportamiento es una clase que implementa IEstado:
using UnityEngine;
public class EstadoPerseguir : IEstado
{
private readonly Enemigo enemigo;
public EstadoPerseguir(Enemigo e) => enemigo = e;
public void Entrar() => enemigo.PonerAnimacion("Correr");
public void Actualizar()
{
enemigo.MoverHacia(enemigo.Jugador.position);
if (!enemigo.VeAlJugador())
enemigo.Maquina.CambiarA(new EstadoPatrullar(enemigo));
else if (enemigo.DistanciaAlJugador() < 1.5f)
enemigo.Maquina.CambiarA(new EstadoAtacar(enemigo));
}
public void Salir() { }
}Y el enemigo solo delega:
public class Enemigo : MonoBehaviour
{
public MaquinaEstados Maquina { get; private set; }
void Awake()
{
Maquina = new MaquinaEstados();
Maquina.CambiarA(new EstadoPatrullar(this));
}
void Update() => Maquina.Tick();
}Ahora "perseguir" vive en EstadoPerseguir, "atacar" en EstadoAtacar, etc. Añadir un comportamiento nuevo es crear una clase, no ampliar un Update gigante. Y las transiciones quedan explícitas dentro de cada estado.
Relación con el Animator#
El Animator es una máquina de estados (visual, para animaciones). Este patrón es la versión en código para la lógica. A menudo van en paralelo: el estado lógico "Atacar" también pone el parámetro del Animator para la animación de ataque.
Cuándo usarla (y cuándo no)#
- Pocos estados (2-3) muy simples → quizá un par de
ifo unenumconswitchbasta; no te compliques - Varios estados con transiciones claras (IA, estado del jugador) → máquina de estados con clases
- Estados muy complejos con jerarquías y comportamientos combinados → considera una FSM jerárquica o un behaviour tree
Errores frecuentes#
- Aplicar el patrón a algo con dos estados triviales (sobreingeniería).
- Estados que se conocen entre sí de más; deja que las transiciones vivan dentro de cada estado.
- Olvidar
Salir()/Entrar()para limpiar y preparar (animaciones, temporizadores). - Guardar estado mutable en el sitio equivocado (el estado del enemigo va en el enemigo, no duplicado en cada
IEstado).
Ponte a prueba#
Resumen#
La máquina de estados separa cada comportamiento (patrullar, perseguir, atacar) en su clase IEstado, con un motor que gestiona el estado activo y sus transiciones Entrar/Actualizar/Salir. Sustituye el Update lleno de if por algo mantenible y ampliable. Úsala cuando haya varios estados con transiciones; para dos triviales, no te compliques. Combínala con el Animator para lógica + animación.