MonoFSM

SkillDev tools

Usage guide for the MonoFSM finite state machine framework. Use when you need to: (1) understand MonoFSM architecture and design philosophy (2) add/modify States, Transitions, Conditions, or Actions in a Unity Scene (3) write new Action or Condition C# scripts (4) use the Auto series Attributes for

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the MonoFSM skill

What this skill tells your AI

The instructions your AI receives, as published by red-candle-games-co-ltd/monofsm in skills/MonoFSM/SKILL.md and read by ahel’s review.

以 GameObject 層級為核心的有限狀態機框架。

核心設計

GameObject 層級表達式:狀態、轉換、動作和條件都是場景中的 GameObject。

[FSM Root]                           # MonoFSMOwner
├── [VarFolder] VariableFolder       # 變數區(VariableFolder + StateMachineLogic)
│   └── f_varName                    # VarFloat / VarEntity 等
│       └── BoundModifier            # VariableFloatBoundModifier(可選)
├── [SchemaFolder] SchemaFolder
├── [StateFolder] StateFolder        # StateMachineLogic 主體
│   └── [State] StateName            # GeneralState
│       ├── OnStateEnterHandler      # 進入時觸發 → 子層放 Action
│       │   └── [Action] XxxAction   # AbstractStateAction 實作
│       ├── [Timer] TimerName        # VarFloatCountDownTimer(可選)
│       └── [Transition] => Target   # TransitionBehaviour
│           └── [Condition] Name     # AbstractConditionBehaviour 實作
└── Context                          # MonoContext

場景編輯(Unity MCP)

直接用 MCP 工具在 Scene 中編輯 FSM,見 references/scene-editing.md。

程式化讀取 FSM 結構

實作 Editor 工具(匯出、視覺化、批次修改)需要 traverse MonoFSM 階層時,見 references/fsm-traversal.md。涵蓋 StateFolder 偵測、變數/狀態/轉換/條件/動作的走訪規則,以及 AnimatorPlayAction 不繼承 AbstractStateAction 等 gotcha。實作範本:MonoFSM/1_MonoFSM_Core/Editor/PrefabExporter/FsmTextExporter.cs。

Auto Attributes

[Auto]                               // GetComponent<T>()
[AutoParent]                         // GetComponentInParent<T>()
[AutoChildren]                       // GetComponentsInChildren<T>()
[AutoChildren(DepthOneOnly = true)]  // 僅直接子物件

欄位型別可以是 interface([AutoParent] private ICurrentEntityOwner _owner;),底層走 GetComponentInParent(Type, true),取到的是最近的一顆 parent,可用來當「多種容器共用同一個 child 元件」的自動接線。

Editor 下 Auto 欄位還沒解析:用 AutoReferenceFieldEditor

Auto 系列在 editor 下要等 Inspector 被點開才會解析,所以 Description、IsValid 這類「畫 hierarchy 就會被呼叫」的成員裡,Auto 欄位常常還是 null → NRE 或一直噴 error。不要自己寫 GetComponentInParent 補,也不要用 if (Application.isPlaying) 迴避,要當場補解析:

[ShowInInspector] [AutoParent] private ICurrentEntityOwner _owner;

private ICurrentEntityOwner Owner
{
    get
    {
        if (_owner == null)
            AutoAttributeManager.AutoReferenceFieldEditor(this, nameof(_owner));
        return _owner;
    }
}
  • AutoAttributeManager 在 global namespace,不用 using;方法標了 [Conditional("UNITY_EDITOR")],build 時整個 call site 被移除,內部又自己 if (Application.isPlaying) return,runtime 零成本
  • 走的是同一顆 attribute 的 Execute,LimitedType / includeSelf 等設定都會被尊重,不會把語意寫死
  • 反射結果進 FieldCache,比每次 GetComponentInParent 便宜
  • 即使補了解析仍可能是 null(真的沒接),呼叫端還是要 null guard;error log 只在 Application.isPlaying 時才印,否則 editor 會刷滿 console
  • 既有範例:AbstractMonoVariable.HasParentVarEntity、MonoEntity._fsmLogic、MonoBlackboard 的各 folder、ValueProvider._parentEntity、VarEntityCurrentItem.Owner

其他常用 Attributes

[Required]       // 必填欄位(Inspector 警告)
[CompRef]        // 標記為組件引用
[DropDownRef]    // 下拉選擇(需手動在 Inspector 設定,MCP 無法設定此類型)
[SOConfig("子資料夾名")] // ScriptableObject 欄位用,提供 Create 按鈕與路徑選擇器

SOConfig 注意事項

[SOConfig] 的 Drawer(SOConfigAttributeDrawer)使用 IList.Add() 新增資產,因此:

  • 集合欄位必須用 List<T>,不可用 T[](原生陣列大小固定,Add() 會拋 NotSupportedException)
  • 範例:[SerializeField] [SOConfig("StateTags")] private List<StateTag> _stateTags = new();

狀態優先級

狀態有 Priority 屬性,高優先級狀態不會被低優先級狀態打斷。

狀態進入條件

優先把「能否進入此 State」的條件放在目標 State 的 CanEnterState。 不要把相同條件分散複製到各個來源 State 的 Transition;這樣多個 State 要轉入同一目標時,只需各自建立轉向該 State 的 Transition,進入資格仍由目標 State 統一維護。

只有條件確實取決於「從哪個來源 State 離開」時,才放在該來源的 Transition。

命名規範

  • SerializeField 和 public field 以底線開頭:_myField
  • 百分比/比例欄位使用 0~1 範圍([Range(0f, 1f)]),不用 0~100
  • 新寫的 component(繼承鏈上有 AbstractDescriptionBehaviour)一律 override Description,把關鍵欄位組成一句話,hierarchy / State 樹才看得懂。細節與陷阱見 references/writing-actions.md

常用組件清單

見 references/components.md。

EffectDealer / EffectReceiver 系統

定義「誰可以對誰造成效果」的互動系統,見 references/effect-system.md。

物件(receiver 端)要讀「誰在跟我互動」身上的值時,走 best match 的 EffectEnterBestMatchNode._hittingEntity,不要取本機玩家;組法與坑見 effect-system.md 的「在物件上取『誰在跟我互動』的 selector entity」。

新增 detector 時放現成的 MonoFSM/0_MonoFSM_Example_Module/[Detector] Trigger.prefab,不要手刻——手刻容易漏掉 kinematic Rigidbody,static-static 的 trigger 完全不觸發且沒有錯誤訊息。細節見 effect-system.md 的「新增 EffectDetector」。

ValueSource / Variable 系統

AbstractValueSource<T> 泛型基類用於每幀計算並提供值(方向、位置、輸入等)。Variable 系統(VarFloat、VarVector3 等)的 IsValueExist 用於判斷 runtime 有效值。詳見 references/value-source.md。

需要「目標位置」時,用 TargetPositionResolver(namespace MonoValueProvider,在 Core),不要在欄位寫死 Transform。它是 [Serializable],統一解析 VarVector3 / VarTransform / VarEntity 三種來源(優先序:Vector3 > Transform > Entity,各自 IsValueExist 才採用)。常用 API:GetTargetPosition(fallback)、ResolvedTransform、HasTarget、ActiveSource、ClearPositionTarget()。用法:欄位宣告 [InlineProperty][HideLabel] public TargetPositionResolver _source = new();,取值前先判 HasTarget。位置:1_MonoFSM_Core/Runtime/0_Pattern/DataProvider/EntityProvider/ValueSource/TargetPositionResolver.cs。

VarWrapper 系列(可綁 Var 或填常數的欄位)

VarFloatWrapper / VarIntWrapper 等 [Serializable] 包裝類,讓欄位在 Inspector 二選一:綁一個 Var 引用,或直接填常數。取值一律用 .Value,宣告預設值用 new(...)(如 private VarIntWrapper _index = new(-1)),namespace 為 MonoFSM.Variable。數值參數欄位一律用 Wrapper,不要並排寫 float _x + VarFloat _xVar。詳見 references/var-wrapper.md。

C# 效能模式

撰寫 MonoFSM 相關 C# 程式碼時的 GC 避免技巧,見 references/csharp-patterns.md。

Serialized 欄位型別遷移

需要把已序列化的欄位改成不同型別(如 VarFloat 直接參照 → VarFloatWrapper)又不想掉 prefab reference 時,見 references/serialization-migration.md。涵蓋為何直接改型別一定掉 ref、legacy 欄位 + FormerlySerializedAs 接舊資料、LoadPrefabContents 批次遷移、驗證與清孤兒資料的完整 6 步流程。

References

檔案什麼情況要讀它
references/writing-actions.md要新寫或修改 Action / Condition 的 C# 腳本時。含 Action / Condition 範本、Description override 慣例、Render behaviour 掛載位置決定觸發時機(多人時 client 跑不跑)、同一功能要同時支援 Action 與 Render 的 Writer 拆法
references/design-patterns.md設計一個新機制、或既有機制會漏狀態/時序出錯時。含 Data-Driven(用 Var 當溝通介面)、持續性狀態改用拉式 Getter + Switch Simulate、Unity 回調 cache 到 Simulate 統一處理、Raycast 一律走 IRaycastProcessor
references/scene-editing.md要在 Unity Scene / prefab 上實際新增或修改 State、Transition、Condition、Action 節點時
references/fsm-traversal.md寫 Editor 工具要程式化走訪 FSM 階層(匯出、視覺化、批次修改)時
references/components.md想知道有哪些現成的 State / Action / Condition / Timer 等組件可以直接用,不用自己寫時
references/effect-system.md處理 EffectDealer / EffectReceiver 互動(誰能對誰造成效果、偵測、判定)時;也含「物件上要取互動者(selector)entity」的組法、新增 EffectDetector 該放哪顆 nested prefab 與 kinematic Rigidbody 的坑
references/value-source.md要做每幀計算並提供值的 AbstractValueSource<T>,或需要理解 Variable 的 IsValueExist / getter 型 IsNull 語意、runtime 寫入的 Var 要勾 _isRuntimeOnly、或 Var 底下要掛多顆 condition(只看第一顆 active 的,不是 OR,要 AND 得包 CompositeCondition)時
references/var-wrapper.md欄位要讓使用者在「綁一個 Var」與「直接填常數」之間二選一(VarFloatWrapper 等)時
references/csharp-patterns.md寫每幀執行的程式碼、需要避免 GC 配置時;也含序列化 array 欄位不會是 null 導致 ??= lazy init 失效的坑
references/serialization-migration.md要改已序列化欄位的型別又不想掉 prefab reference 時

Signals

GitHub stars
25
Forks
2
Last commit
Sep 2026
Advanced
Item type
skill
Key
monofsm
Source
github.com/red-candle-games-co-ltd/monofsm