mcpField
U
unity-state-machines

unity-state-machines

github.com/nice-wolf-studio开发4073 次安装6 周前更新
代码仓库

关于 unity-state-machines

面向 Unity 开发者的状态与行为系统决策技能,帮助为游戏流程、AI 和 UI 导航选择合适的状态模式。涵盖 FSM、HFSM、行为树与状态栈,并强调可测试的纯 C# 状态逻辑。

主要功能

  1. 01比较 Animator FSM、代码 FSM、HFSM、行为树和状态栈
  2. 02提供基于状态数量与转换类型的选型建议
  3. 03说明类状态机与层级状态机的架构
  4. 04涵盖行为树及栈式状态机实现模式
  5. 05介绍无需场景的状态逻辑测试

适用场景

  1. 01为角色动画以外的游戏逻辑选择状态系统
  2. 02构建含分组子状态的战斗状态机
  3. 03设计可中断、按优先级决策的复杂 AI
  4. 04实现可返回上一界面的 UI 导航

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-state-machines

安装后在 Claude Code 中输入/unity-state-machines确认已加载。

附带文件references/state-system-scaffolds.md

详细介绍

状态与行为系统 —— 决策模式

前置技能: unity-game-architecture(MonoBehaviour 与纯 C#、组件组合)、unity-animation(Animator FSM、StateMachineBehaviour)、unity-scripting(MonoBehaviour 生命周期)

这些模式针对最常见的状态管理问题:Claude 编写临时的 if/else 链或结构混乱的巨型 switch 语句,导致状态逻辑在超过 3-4 个状态后便难以维护。


模式:状态系统选择

适用场景:实现 AI 行为、游戏流程、角色状态或 UI 导航

决策:

  • Animator FSM —— 状态与动画关联。适用于角色移动、攻击连招、死亡动画。可视化图表对设计师友好。完整内容请参阅 unity-animation —— 不要仅为驱动动画而构建代码 FSM。
  • 代码 FSM(基于 enum 或类) —— 适用于游戏流程(MainMenu/Playing/Paused/GameOver)、技能系统、回合阶段。完全可测试,且无 Animator 开销。当状态具有复杂逻辑而非仅仅切换动画时使用。
  • 分层 FSM(HFSM) —— 状态中包含子状态。战斗包含 {Melee、Ranged、Blocking}。当扁平 FSM 中相关状态之间的转换过多时使用。
  • 行为树(BT) —— 适用于需要优先级、可中断序列和并行行为的复杂 AI。当 FSM 超过 8-10 个状态,且转换爆炸导致图表难以阅读时使用。
  • 基于栈的状态机(下推式) —— 适用于 UI 屏幕、暂停菜单、模态对话框。状态通过压入/弹出切换,先前状态会保留并恢复。
How many states? What kind of transitions?
|
+-- 2-3 states, simple toggling?
|     --> if/else or bool flags (don't over-engineer)
|
+-- 4-8 states, clear transitions?
|     --> Code FSM (enum or IState)
|
+-- States group naturally into clusters?
|     --> HFSM (Locomotion > {Idle,Walk,Run}, Combat > {Melee,Ranged})
|
+-- AI with priorities, interrupts, parallel tasks?
|     --> Behavior Tree
|
+-- Need to return to previous state (back button, unpause)?
|     --> Stack-Based State Machine
|
+-- States tied to animations?
      --> Animator FSM (use unity-animation skill)

注意:Animator FSM 即使没有播放任何动画,也会产生逐帧评估开销。不要将 Animator 用于纯逻辑状态机(游戏流程、回合系统)。应改用代码 FSM。


模式:基于类的 FSM 架构

适用场景:构建包含 3 个以上状态的代码 FSM

决策:

  • Enum + switch —— 状态数不超过约 5 个,且转换逻辑很简单时使用。保持简单。
  • IState 接口 + 字典 —— 状态数为 5 个或以上,且每个状态都有复杂的 Enter/Tick/Exit 逻辑时使用。状态应为纯 C# 类(可在 MonoBehaviour 之外进行测试)。

框架(Enum FSM —— 简单):

public enum GameState { MainMenu, Playing, Paused, GameOver }

public class GameFlowManager : MonoBehaviour
{
    private GameState _state = GameState.MainMenu;

    public void ChangeState(GameState newState)
    {
        ExitState(_state);
        _state = newState;
        EnterState(_state);
    }

    void Update()
    {
        switch (_state)
        {
            case GameState.Playing:
                // game logic
                break;
            case GameState.Paused:
                // pause logic
                break;
        }
    }

    void EnterState(GameState state) { /* ... */ }
    void ExitState(GameState state) { /* ... */ }
}

框架(IState FSM —— 可扩展):

// See references/state-system-scaffolds.md for complete implementation
public interface IState
{
    void Enter();
    void Tick(float deltaTime);
    void Exit();
}

public class StateMachine
{
    private IState _currentState;

    public void ChangeState(IState newState)
    {
        _currentState?.Exit();
        _currentState = newState;
        _currentState?.Enter();
    }

    public void Tick(float deltaTime) => _currentState?.Tick(deltaTime);
}

注意:状态应为纯 C# 类,而不是 MonoBehaviour。所属的 MonoBehaviour 通过构造函数或接口将共享上下文对象(transform、rigidbody、引用)传递给状态。这样既能让状态易于测试,也能避免空 MonoBehaviour 的性能开销。


模式:分层 FSM(HFSM)

适用场景:状态自然地归属于某些超状态(Locomotion 包含 Idle/Walk/Run/Sprint)

决策:

  • 嵌套 StateMachine —— 超状态包含自己的 StateMachine。划分清晰,最适合两层深度的层级结构。
  • 使用守卫条件扁平化 —— 如果层级会达到 3 层或以上,HFSM 的复杂度可能超过行为树。可以考虑切换到 BT。

框架:

// A super-state that contains a child state machine
public class HierarchicalState : IState
{
    private readonly StateMachine _subMachine;
    private readonly IState _defaultSubState;

    public HierarchicalState(StateMachine subMachine, IState defaultSubState)
    {
        _subMachine = subMachine;
        _defaultSubState = defaultSubState;
    }

    public void Enter() => _subMachine.ChangeState(_defaultSubState);
    public void Tick(float deltaTime) => _subMachine.Tick(deltaTime);
    public void Exit() => _subMachine.ChangeState(null); // Exit current sub-state
}

// Usage:
// var idleState = new IdleState(context);
// var walkState = new WalkState(context);
// var locomotionSubMachine = new StateMachine();
// var locomotionState = new HierarchicalState(locomotionSubMachine, idleState);
// mainMachine.ChangeState(locomotionState);

注意:超状态的 Exit 必须传递到当前激活的子状态。退出顺序:sub-state.Exit() -> super-state.Exit()。两个层级都可以存在转换:子状态转换(Idle -> Walk)仍在超状态内部,而超状态转换(Locomotion -> Combat)会退出整个层级结构。


模式:行为树架构

适用场景:AI 需要基于优先级的决策,并支持可中断序列

决策:

  • 自定义精简 BT —— 节点少于 20 个,且需要完全控制、不依赖外部库时使用。构建 Selector、Sequence、ActionLeaf。
  • 第三方 BT 库(NodeCanvas、Behavior Designer、Fluid BT)—— 支持可视化编辑,对设计师友好,提供 50 多种节点类型。当由非程序员设计 AI 时,依赖此类库是值得的。

框架(精简 BT):

// See references/state-system-scaffolds.md for complete implementation
public enum BTStatus { Success, Failure, Running }

public abstract class BTNode
{
    public abstract BTStatus Tick(Blackboard bb);
}

// Selector: tries children in order, succeeds on first success
// Sequence: runs children in order, fails on first failure
// ActionLeaf: executes a single action

注意:行为树每帧都会从根节点开始 tick。为提升性能,可以缓存“运行中”的节点,并从该节点恢复执行,而不是重新评估整棵树。大型 BT(100 多个节点)应使用 Blackboard 共享数据,而非闭包。Blackboard 本质上只是一个带有类型化访问器的 Dictionary<string, object>。


模式:基于栈的状态机

适用场景:UI 导航、暂停菜单、模态对话框、可撤销的状态转换

决策:

  • 栈机器——需要返回先前状态时使用。例如:游戏进行中 -> 暂停 -> 游戏进行中。游戏进行中 -> 背包 -> 制作 -> 背包 -> 游戏进行中。
  • 扁平 FSM——转换不具有栈式特征时使用(永远不会“返回先前所在的位置”)。

脚手架:

public class StateStack
{
    private readonly Stack<IState> _stack = new();

    public IState Current => _stack.Count > 0 ? _stack.Peek() : null;

    public void Push(IState state)
    {
        Current?.Pause();  // Pause current (not Exit -- it stays on the stack)
        _stack.Push(state);
        state.Enter();
    }

    public void Pop()
    {
        if (_stack.Count == 0) return;
        var popped = _stack.Pop();
        popped.Exit();
        Current?.Resume();  // Resume the state underneath
    }

    public void Tick(float deltaTime) => Current?.Tick(deltaTime);
}

// Extended state interface for stack machines
public interface IStackableState : IState
{
    void Pause();   // Called when a new state is pushed on top
    void Resume();  // Called when the state above is popped
}

注意事项:如果忘记弹出状态,栈会无限增长。请设置最大深度(例如 10),并记录警告。清空栈时(例如返回主菜单),按顺序弹出所有状态,以便每个状态都调用 Exit。切勿将同一个状态实例压入栈两次——应创建新实例,或使用标记防止重复压入。


模式:测试状态机

适用场景:为 FSM 或 BT 逻辑编写测试

决策:状态是纯 C# 类 -> 使用 NUnit Edit Mode 测试。无需 MonoBehaviour、Play Mode 或场景。

脚手架:

// State that returns a transition signal
public class IdleState : IState
{
    private readonly EnemyContext _ctx;
    public bool ShouldTransitionToChase { get; private set; }

    public IdleState(EnemyContext ctx) => _ctx = ctx;

    public void Enter() => ShouldTransitionToChase = false;

    public void Tick(float deltaTime)
    {
        if (_ctx.DistanceToPlayer < _ctx.DetectionRange)
            ShouldTransitionToChase = true;
    }

    public void Exit() { }
}

// Test (Edit Mode, no Unity runtime needed)
[Test]
public void IdleState_TransitionsToChase_WhenPlayerInRange()
{
    var ctx = new EnemyContext { DistanceToPlayer = 5f, DetectionRange = 10f };
    var idle = new IdleState(ctx);

    idle.Enter();
    idle.Tick(0.016f);

    Assert.IsTrue(idle.ShouldTransitionToChase);
}

[Test]
public void IdleState_StaysIdle_WhenPlayerOutOfRange()
{
    var ctx = new EnemyContext { DistanceToPlayer = 15f, DetectionRange = 10f };
    var idle = new IdleState(ctx);

    idle.Enter();
    idle.Tick(0.016f);

    Assert.IsFalse(idle.ShouldTransitionToChase);
}

注意事项:如果状态依赖 Time.deltaTime,请将 float deltaTime 作为参数注入 Tick()。如果状态需要使用 Unity API(如 Physics.Raycast),请通过接口封装这些 API,以便测试提供存根。另请参阅:unity-testing,了解 Test Framework 的设置。


对比表

特性枚举 FSMIState FSMHFSM行为树栈机器
复杂度低中中高高中
最适合2-5 个简单状态5-15 个状态分组状态复杂 AIUI 导航
可测试性测试 switch逐状态测试逐状态 + 层级测试逐节点测试压栈/弹栈测试
设计师友好否否否使用可视化编辑器时否
转换管理在 switch 中手动管理字典或信号按层级转换基于优先级压栈/弹栈
内存最少每个状态一个实例嵌套机器内存中保存完整树栈深度
性能O(1) switchO(1) 委托每次 tick 为 O(depth)每次 tick 为 O(tree)O(1)
应避免的情况状态数 >5与动画绑定的状态层级数 >2状态数 <8非栈式转换

相关技能

  • unity-animation——Animator FSM、StateMachineBehaviour、混合树(状态驱动动画时使用)
  • unity-game-architecture——MonoBehaviour 与纯 C# 的区别(状态应使用纯 C#)、Service Locator
  • unity-ai-navigation——NavMeshAgent 与 FSM/BT 集成,实现 AI 移动
  • unity-testing——使用 NUnit Edit Mode 测试状态逻辑

其他资源

评论

支持 Markdown