lunes, 16 de junio de 2025

Patrón State en Unity: IA y jugador (sin if gigantes)



Este post va dedicado a todos los que alguna vez nos tuvimos que pelear con una MEF. 

Problema clásico: un personaje tiene estados (Idle, Run, Jump, Attack…). Con tantos if/else el código explota y el rendimiento más.
Solución: encapsula cada estado en su clase y cambia de uno a otro con una máquina de estados.

Consejillos para no perderse: puedes añadir estados sin tocar los existentes y te aconsejo que hagas un esquema en un papel de estados a la "vieja usanza" antes de codificar.

Interfaz de estado

public interface IState { void Enter(); void Tick(); void Exit(); }

StateMachine reutilizable

using UnityEngine; public class StateMachine : MonoBehaviour { IState _current; public void Set(IState next) { _current?.Exit(); _current = next; _current?.Enter(); } void Update() => _current?.Tick(); }

Estados concretos (ejemplo jugador)

using UnityEngine; public class PlayerContext : MonoBehaviour { public Rigidbody rb; public float moveSpeed = 5f; public bool IsGrounded; public Vector2 MoveInput; public bool JumpPressed; } public class IdleState : IState { readonly PlayerContext ctx; readonly StateMachine sm; public IdleState(PlayerContext c, StateMachine m){ ctx=c; sm=m; } public void Enter() { /* anim Idle */ } public void Tick() { if (ctx.MoveInput.sqrMagnitude > 0.01f) sm.Set(new RunState(ctx, sm)); if (ctx.JumpPressed && ctx.IsGrounded) sm.Set(new JumpState(ctx, sm)); } public void Exit() {} } public class RunState : IState { readonly PlayerContext ctx; readonly StateMachine sm; public RunState(PlayerContext c, StateMachine m){ ctx=c; sm=m; } public void Enter(){ /* anim Run */ } public void Tick() { var dir = new Vector3(ctx.MoveInput.x, 0, ctx.MoveInput.y); ctx.rb.velocity = new Vector3(dir.x * ctx.moveSpeed, ctx.rb.velocity.y, dir.z * ctx.moveSpeed); if (dir.sqrMagnitude < 0.01f) sm.Set(new IdleState(ctx, sm)); if (ctx.JumpPressed && ctx.IsGrounded) sm.Set(new JumpState(ctx, sm)); } public void Exit(){} } public class JumpState : IState { readonly PlayerContext ctx; readonly StateMachine sm; bool jumped; public JumpState(PlayerContext c, StateMachine m){ ctx=c; sm=m; } public void Enter(){ jumped=false; } public void Tick() { if (!jumped){ ctx.rb.AddForce(Vector3.up * 5f, ForceMode.VelocityChange); jumped = true; } if (ctx.IsGrounded && ctx.rb.velocity.y <= 0.01f) sm.Set(new IdleState(ctx, sm)); } public void Exit(){} }

Bootstrap

using UnityEngine; [RequireComponent(typeof(StateMachine), typeof(PlayerContext))] public class PlayerBootstrap : MonoBehaviour { void Start() { var sm = GetComponent<StateMachine>(); var ctx = GetComponent<PlayerContext>(); sm.Set(new IdleState(ctx, sm)); } }


No hay comentarios:

Publicar un comentario