一、模式概述
意图
允许对象在内部状态改变时改变其行为,看起来好像改变了它的类。
1.1 核心角色
- IState<T>(State 接口)
定义状态通用方法:Enter、Exit、Update。 - ConcreteState
各具体状态类,实现接口并封装状态专属逻辑。 - StateMachine<TContext>(Context)
持有当前状态,负责状态切换;为状态提供共享数据上下文。 - Client(使用者)
将StateMachine持有在 MonoBehaviour 中,在Update()中驱动当前状态。
1.2 UML 类图

二、通用 StateMachine 实现
/// <summary>
///状态接口,T 是 Context 类型
///</summary>
public interface IState<T> {
void Enter(T ctx);
void Exit(T ctx);
void Update(T ctx);
}
/// <summary>
///通用状态机
///</summary>
public class StateMachine<T> {
private T _context;
private IState<T> _current;
public void Initialize(T context, IState<T> initialState) {
_context = context;
_current = initialState;
_current.Enter(_context);
}
public void ChangeState(IState<T> nextState) {
if (_current != null) _current.Exit(_context);
_current = nextState;
_current.Enter(_context);
}
public void Update() {
_current?.Update(_context);
}
public IState<T> CurrentState => _current;
}
- Initialize:在
Awake()或Start()时设定初始状态; - ChangeState:统一管理
Exit→切换→Enter流程; - Update:在 MonoBehaviour 的
Update()中调用。
三、Unity 实战:角色状态机
3.1 角色 Context
public class Character : MonoBehaviour {
[HideInInspector] public StateMachine<Character> StateMachine;
public float walkSpeed = 2f;
public float runSpeed = 5f;
public float jumpForce = 7f;
private Rigidbody _rb;
void Awake() {
_rb = GetComponent<Rigidbody>();
StateMachine = new StateMachine<Character>();
}
void Start() {
StateMachine.Initialize(this, new IdleState());
}
void Update() {
StateMachine.Update();
}
}
3.2 具体状态:Idle、Run、Jump
public class IdleState : IState<Character> {
public void Enter(Character ctx) {
ctx.GetComponent<Animator>().Play("Idle");
}
public void Exit(Character ctx) { }
public void Update(Character ctx) {
var h = Input.GetAxis("Horizontal");
var v = Input.GetAxis("Vertical");
if (new Vector2(h, v).sqrMagnitude > 0.01f) {
ctx.StateMachine.ChangeState(new RunState());
} else if (Input.GetButtonDown("Jump")) {
ctx.StateMachine.ChangeState(new JumpState());
}
}
}
public class RunState : IState<Character> {
public void Enter(Character ctx) {
ctx.GetComponent<Animator>().Play("Run");
}
public void Exit(Character ctx) { }
public void Update(Character ctx) {
var dir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")).normalized;
ctx.transform.Translate(dir * ctx.runSpeed * Time.deltaTime, Space.World);
if (dir.sqrMagnitude < 0.01f) {
ctx.StateMachine.ChangeState(new IdleState());
} else if (Input.GetButtonDown("Jump")) {
ctx.StateMachine.ChangeState(new JumpState());
}
}
}
public class JumpState : IState<Character> {
private bool _started;
public void Enter(Character ctx) {
ctx.GetComponent<Animator>().Play("Jump");
ctx._rb.AddForce(Vector3.up * ctx.jumpForce, ForceMode.VelocityChange);
_started = true;
}
public void Exit(Character ctx) { }
public void Update(Character ctx) {
// 跳跃阶段结束:检测落地
if (_started && ctx._rb.velocity.y <= 0 && ctx.IsGrounded()) {
ctx.StateMachine.ChangeState(new IdleState());
}
}
}
提示:
IsGrounded()可用射线检测或碰撞回调实现。
四、高级话题与扩展
4.1 状态复用与单例状态
如果状态无内部数据,可将状态实例设为单例,避免频繁 new:
public class IdleState : IState<Character> {
public static readonly IdleState Instance = new IdleState();
private IdleState() { }
// … 实现 Enter/Exit/Update
}
...
ctx.StateMachine.ChangeState(IdleState.Instance);
4.2 基于 ScriptableObject 的状态
利用 SO 资产定义状态,便于策划在 Inspector 编辑动画剪辑、参数等:
public abstract class SOState : ScriptableObject, IState<Character> {
public abstract void Enter(Character ctx);
public abstract void Exit(Character ctx);
public abstract void Update(Character ctx);
}
4.3 层级状态机(Hierarchical State Machine)
- 父状态:定义公共 Enter/Exit;
- 子状态:在父状态内部切换,支持复用行为。
- 实现:StateMachine 嵌套或在状态内部持有子
StateMachine。
4.4 状态超时与触发器
- 在
Enter记录时间戳t0; - 在
Update中若Time.time - t0 > duration,自动切换; - 用枚举或事件触发全局状态变更。
五、性能与调试
GC 控制
- 尽量重用状态实例,避免每帧
new;
- 尽量重用状态实例,避免每帧
Profiler
- 确认
StateMachine.Update()调用开销;
- 确认
调试可视化
- 在 Inspector 中显示当前状态:
[ReadOnly] public string currentStateName; void Update() { currentStateName = StateMachine.CurrentState.GetType().Name; }日志
- 在
ChangeState中打印日志,快速定位错误切换。
- 在
六、注意事项
| 场景 | 陷阱 | 建议 |
|---|---|---|
| 状态爆炸 | 状态类数量过多,且相似逻辑未复用 | 抽象公共逻辑到基类或组件,使用组合+策略减少状态数量 |
| 状态切换逻辑分散 | 条件判断散落在多个状态,实现复杂且难以维护 | 统一在 Context 或 状态机配置表(ScriptableObject)中管理 |
| 频繁 new/GC | 每次切换都 new 状态对象,导致 GC 压力 | 使用单例状态或状态池,重用状态实例 |
| Update 调用冗余 | 在无状态或状态不需 Update 时仍调用,浪费运算 | 在状态机中支持 HasUpdate 标志,跳过无需更新的状态 |
| 多线程/协程冲突 | 在协程或异步中切换状态时,主线程 Update 也会访问状态机 | 确保所有状态切换和 Update 均在主线程执行 |
七、小结
- 职责单一:每个状态只封装自身行为,Enter/Exit/Update 清晰分离;
- 状态实例重用:无数据状态可用单例,带数据状态可用池化;
- 配置驱动:ScriptableObject 状态+Inspector 组合,方便无代码扩展;
- 层次化设计:用父子状态或子状态机复用公共逻辑;
- 可视化与日志:在运行时实时查看当前状态与切换日志,提升调试效率。