Transiciones
Parte de la sección Arquitectura de juegos en Unity del Journey de Unity de Coddy. Lección 30 de 50.
¿Quién decide cuándo cambia el estado? Preguntarle al propietario vuelve a poner todas las reglas en un solo Update, como hacía el switch. La respuesta habitual es que cada estado conoce su propia salida: en su Tick comprueba su condición y le pide a la máquina el siguiente estado. Esta bomba espera hasta que el jugador se acerca, entonces quema la mecha y vuelve a esperar si el jugador huye:
public class Bomb : MonoBehaviour
{
public Transform player;
public StateMachine machine = new StateMachine();
public IState idle;
public IState fuse;
void Start()
{
idle = new IdleState(this);
fuse = new FuseState(this);
machine.ChangeState(idle);
}
void Update() { machine.Tick(); }
public bool PlayerNear()
{
return Vector3.Distance(transform.position, player.position) < 3f;
}
}
public class IdleState : IState
{
private Bomb bomb;
public IdleState(Bomb bomb) { this.bomb = bomb; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
if (bomb.PlayerNear()) bomb.machine.ChangeState(bomb.fuse);
}
}
public class FuseState : IState
{
private Bomb bomb;
private float timer;
public FuseState(Bomb bomb) { this.bomb = bomb; }
public void Enter() { timer = 2f; }
public void Exit() { }
public void Tick()
{
if (!bomb.PlayerNear())
{
bomb.machine.ChangeState(bomb.idle);
return;
}
timer -= Time.deltaTime;
if (timer <= 0f)
{
Debug.Log("Boom!");
Object.Destroy(bomb.gameObject);
}
}
}Cada estado recibe la bomba en su constructor y accede a la máquina, la comprobación del jugador y los otros estados a través de ella. La bomba crea cada estado una sola vez, en Start, así que un estado nunca crea otro con new: dos estados que se crearan mutuamente en sus constructores entrarían en un bucle hasta que se desbordara la pila. El Update de la bomba solo ejecuta el Tick de la máquina, y las reglas viven en los estados a los que pertenecen: la mecha de 2 segundos es responsabilidad exclusiva de FuseState.
ChangeState ejecuta Exit de FuseState y Enter de IdleState de inmediato, pero el resto del Tick de FuseState seguiría ejecutándose después de la llamada. return lo detiene; de lo contrario, la mecha ardería un fotograma más en un estado que ya ha terminado. Destroy se escribe Object.Destroy: un MonoBehaviour lo hereda de la clase Object de Unity, y una clase simple tiene que nombrar la clase.
Desafío
IntermedioLa venus atrapamoscas se cierra de golpe contra cualquiera que se le acerque. Flytrap.cs está listo: crea la máquina y los tres estados (los campos públicos open, snap y chew), inicia Open y actualiza la máquina en cada fotograma, y PlayerInReach() indica si el jugador está a menos de 2 unidades de distancia por el suelo. En FlytrapStates.cs están escritos todos los métodos Enter (los colores, los temporizadores, el registro Chomp!). Escribe las transiciones en el Tick de cada estado:
- Open: cuando el jugador está al alcance, la trampa se cierra de golpe.
- Snap dura 0.5 segundos. Después, la trampa mastica si el jugador sigue al alcance y vuelve a abrirse si logró escapar.
- Chew dura 3 segundos, sin importar quién esté cerca; después, la trampa vuelve a abrirse.
Las flechas mueven al Player. Pulsa Play, camina hacia la trampa, luego intenta esquivarla y, después, pulsa Check.
Pruébalo tú mismo
using UnityEngine;
public class OpenState : IState
{
private Flytrap trap;
public OpenState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
trap.Paint(new Color(0.3f, 0.8f, 0.2f));
}
public void Tick()
{
// Ciérrate de golpe cuando el jugador esté al alcance
}
public void Exit()
{
}
}
public class SnapState : IState
{
private Flytrap trap;
private float timer;
public SnapState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
timer = 0f;
trap.Paint(new Color(1f, 0.55f, 0.1f));
}
public void Tick()
{
// Después de 0.5 segundos: mastica si el jugador sigue al alcance; si no, vuelve a abrirte
}
public void Exit()
{
}
}
public class ChewState : IState
{
private Flytrap trap;
private float timer;
public ChewState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
timer = 3f;
trap.Paint(new Color(0.5f, 0.05f, 0.1f));
Debug.Log("Chomp!");
}
public void Tick()
{
// Después de 3 segundos: vuelve a abrirte
}
public void Exit()
{
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Arquitectura de juegos en Unity
1Componentes, no jerarquías
Componentes pequeñosUn script base para enemigosComponentes abstractosComponentes que trabajan juntosRepaso: torreta modular4Datos serializados
Clases serializablesListas en el InspectorEnumeraciones en el InspectorRepaso: tabla de botín7Máquinas de estados
Estados como clasesUna máquina de estadosTransicionesCerebros de enemigosRepaso: estados de puertas10Proyecto: Superviviente de la arena
El héroeTipos de enemigos2Interfaces en Unity
IDamageableInterfaces y TryGetComponentGetComponentsRepaso: tecla de interacción5ScriptableObjects
Recursos de datosEstadísticas de armasConfiguración compartidaOleadas como datosRepaso: base de datos de objetos