mcpField
U
unity-state-machines

unity-state-machines

github.com/nice-wolf-studio開発4073 回インストール6 週間前に更新
リポジトリ

unity-state-machines について

ゲームフロー、AI、UI ナビゲーション向けの状態・振る舞いシステムを選ぶ Unity 開発者向けスキルです。FSM、階層型 FSM、ビヘイビアツリー、スタック方式を扱い、テスト可能なプレーン C# の状態ロジックを重視します。

主な機能

  1. 01Animator FSM、コード FSM、HFSM、ビヘイビアツリー、状態スタックの比較
  2. 02状態数と遷移形式に応じた選択指針
  3. 03クラスベース FSM と階層型 FSM の設計
  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 個を超える状態を管理できなくなるという問題です。


PATTERN:状態システムの選択

WHEN:AI の振る舞い、ゲームの進行、キャラクターの状態、UI ナビゲーションを実装するとき

DECISION:

  • Animator FSM ― 状態がアニメーションに関連付けられている場合。キャラクターの移動、攻撃コンボ、死亡アニメーションなどです。デザイナーが扱いやすいビジュアルグラフです。詳しくは unity-animation を参照してください。アニメーションを動かすためだけにコード FSM を構築しないでください。
  • コード FSM(enum またはクラスベース) ― ゲームの進行(MainMenu/Playing/Paused/GameOver)、アビリティシステム、ターンフェーズなどです。完全にテスト可能で、Animator のオーバーヘッドもありません。状態に単なるアニメーションの切り替えではなく、複雑なロジックがある場合に使用します。
  • 階層型 FSM(HFSM) ― 状態の中に状態がある場合です。Combat に {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 を構築してください。


PATTERN:クラスベース FSM アーキテクチャ

WHEN:3 つを超える状態を持つコード FSM を構築するとき

DECISION:

  • Enum + switch ― 遷移ロジックが最小限の、最大 5 状態程度の場合です。シンプルに保ちます。
  • IState インターフェース + dictionary ― 5 状態以上で、各状態に複雑な Enter/Tick/Exit ロジックがある場合です。状態はプレーンな C# クラスにすることで、MonoBehaviour なしでテストできます。

SCAFFOLD(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) { /* ... */ }
}

SCAFFOLD(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 によるパフォーマンスオーバーヘッドを避けられます。


PATTERN:階層型 FSM(HFSM)

WHEN:状態を自然にスーパー状態にまとめられるとき(Locomotion に Idle/Walk/Run/Sprint が含まれる場合など)

DECISION:

  • ネストされた StateMachine ― スーパー状態が独自の StateMachine を持ちます。明確に分離できます。階層が 2 レベルの場合に最適です。
  • ガード条件でフラット化 ― 階層が 3 レベル以上になる場合は、HFSM の複雑さがビヘイビアツリーを上回る可能性があります。BT への切り替えを検討してください。

SCAFFOLD:

// 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 は、アクティブなサブ状態にも伝播させる必要があります。Exit の順序は、sub-state.Exit() -> super-state.Exit() です。遷移は両方のレベルに設定できます。サブ状態の遷移(Idle -> Walk)はスーパー状態内にとどまり、スーパー状態の遷移(Locomotion -> Combat)は階層全体を終了します。


PATTERN:ビヘイビアツリーのアーキテクチャ

WHEN:AI に割り込み可能なシーケンスを伴う、優先順位ベースの意思決定が必要なとき

DECISION:

  • 最小限のカスタム BT ― ノード数が 20 未満で、完全な制御を求め、外部依存関係を避けたい場合です。Selector、Sequence、ActionLeaf を構築します。
  • サードパーティ製 BT ライブラリ(NodeCanvas、Behavior Designer、Fluid BT)― ビジュアル編集、デザイナー向けの操作性、50 種類以上のノードが必要な場合です。プログラマー以外が AI を設計するなら、依存関係を追加する価値があります。

SCAFFOLD(最小限の 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

注意点:ビヘイビアツリーは毎フレーム、ルートからティックします。パフォーマンス向上のため、ツリー全体を再評価するのではなく、「実行中」のノードをキャッシュして、そこから再開します。大規模な BT(ノード数 100 以上)では、クロージャーではなく共有データ用の Blackboard を使用してください。Blackboard は、型付きアクセサーを備えた Dictionary<string, object> にすぎません。


パターン:スタックベースのステートマシン

WHEN:UI ナビゲーション、一時停止メニュー、モーダルダイアログ、元に戻せる状態遷移

DECISION:

  • スタックマシン -- 前の状態に戻る必要がある場合です。ゲームプレイ -> 一時停止 -> ゲームプレイ。ゲームプレイ -> インベントリ -> クラフト -> インベントリ -> ゲームプレイ。
  • フラット FSM -- 遷移がスタック形式ではない場合です(「元いた場所に戻る」ことがありません)。

SCAFFOLD:

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
}

GOTCHA:スタックからのポップを忘れると、スタックが際限なく大きくなります。最大深度(例:10)を設定し、警告をログに記録してください。スタックをクリアする場合(例:メインメニューに戻る場合)は、各状態で Exit が呼び出されるよう、順番にすべてポップしてください。同じ状態インスタンスを二度プッシュしないでください。新しいインスタンスを作成するか、フラグを使って二重プッシュを防いでください。


パターン:ステートマシンのテスト

WHEN:FSM または BT のロジックをテストする場合

DECISION:状態は通常の C# クラスです -> NUnit Edit Mode テストでテストします。MonoBehaviour、Play Mode、シーンは不要です。

SCAFFOLD:

// 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);
}

GOTCHA:状態が Time.deltaTime に依存する場合は、代わりに float deltaTime を Tick() のパラメーターとして渡してください。状態で Unity API(Physics.Raycast)が必要な場合は、インターフェースの背後にラップして、テストでスタブを渡せるようにしてください。Test Framework のセットアップについては unity-testing を参照してください。


比較表

特徴Enum FSMIState FSMHFSMビヘイビアツリースタックマシン
複雑さ低中中~高高中
最適な用途2~5 個の単純な状態5~15 個の状態グループ化された状態複雑な AIUI ナビゲーション
テスト容易性switch のテスト状態ごとのテスト状態ごと+階層のテストノードごとのテストプッシュ/ポップのテスト
デザイナー向けいいえいいえいいえビジュアルエディターを使用いいえ
遷移管理switch 内で手動Dictionary またはシグナルレベルごとの遷移優先度ベースプッシュ/ポップ
メモリ最小限状態ごとのインスタンスネストされたマシンツリー全体をメモリに保持スタックの深さ
パフォーマンスO(1) の switchO(1) のデリゲートティックごとに O(深度)ティックごとに O(ツリー)O(1)
避けるべき場合状態が 5 個を超える場合アニメーションに結び付いた状態階層が 2 レベルを超える場合状態が 8 個未満の場合スタック形式ではない遷移

関連スキル

  • unity-animation -- Animator FSM、StateMachineBehaviour、ブレンドツリー(状態がアニメーションを制御する場合に使用)
  • unity-game-architecture -- MonoBehaviour と通常の C# の比較(状態は通常の C# にする)、Service Locator
  • unity-ai-navigation -- AI の移動における FSM/BT と NavMeshAgent の統合
  • unity-testing -- 状態ロジックの NUnit Edit Mode テスト

追加リソース

コメント