🎨

【Unity】デザインパターン State プレイヤー管理編

2024/11/30に公開

Stateパターンは、オブジェクトが持つ状態(ステート)ごとに異なる振る舞いを管理するためのデザインパターンです。このパターンを実装する方法として、2つのアプローチがあります。

  1. enum + switch を使ったシンプルな実装
  2. クラスを使った本格的なStateパターンの実装

ここでは、それぞれの書き方をまとめています。


1. enum + switch を使ったシンプルな実装

状態をenumで定義し、switch文でそれぞれの状態に応じた処理を記述する方法です。この方法は実装が簡単で、状態が少ない場合に適しています。

実装例

public enum PlayerState
{
    Idle,  // 静止中
    Walk,  // 歩行中
    Jump   // ジャンプ中
}

public class PlayerController : MonoBehaviour
{
    private PlayerState currentState = PlayerState.Idle;

    private void Update()
    {
        // 現在の状態に応じた処理を実行
        switch (currentState)
        {
            case PlayerState.Idle:
                HandleIdle();
                break;
            case PlayerState.Walk:
                HandleWalk();
                break;
            case PlayerState.Jump:
                HandleJump();
                break;
        }

        // 状態遷移の例
        if (Input.GetKeyDown(KeyCode.Space))
        {
            currentState = PlayerState.Jump;
        }
    }

    private void HandleIdle() => Debug.Log("Idle");
    private void HandleWalk() => Debug.Log("Walk");
    private void HandleJump() => Debug.Log("Jump");
}

特徴

  • 状態ごとの処理を1つのクラスにまとめて記述でき、構造が単純です。
  • 状態が増えると、switch文が肥大化しやすく、コードが複雑化します。

2. クラスを使った本格的なStateパターンの実装

状態ごとに独立したクラスを作成し、それらを管理するStateMachineクラスを利用する方法です。このアプローチは、状態が多くなる場合やロジックが複雑な場合に適しています。

実装例

インターフェースと各状態クラス


public interface IState
{
    void Enter();  // 状態に入るときの処理
    void Update(); // 毎フレームの処理
    void Exit();   // 状態から出るときの処理
}

public class IdleState : IState
{
    public void Enter() => Debug.Log("Idle: Enter");
    public void Update() => Debug.Log("Idle: Update");
    public void Exit() => Debug.Log("Idle: Exit");
}

public class WalkState : IState
{
    public void Enter() => Debug.Log("Walk: Enter");
    public void Update() => Debug.Log("Walk: Update");
    public void Exit() => Debug.Log("Walk: Exit");
}

StateMachineとプレイヤー制御


public class PlayerController : MonoBehaviour
{
    private StateMachine stateMachine; // プレイヤーの状態を管理するStateMachine

    private void Start()
    {
        stateMachine = new StateMachine(); // StateMachineのインスタンスを作成
        stateMachine.ChangeState(new IdleState()); // 初期状態をIdleStateに設定
    }

    private void Update()
    {
        stateMachine.Update(); // 現在の状態のUpdateメソッドを呼び出す

        if (Input.GetKeyDown(KeyCode.Space))
        {
            stateMachine.ChangeState(new WalkState()); // スペースキーが押されたらWalkStateに変更
        }
    }
}

public class StateMachine
{
    private IState currentState; // 現在アクティブな状態を保持するフィールド

    public void ChangeState(IState newState)
    {
        currentState?.Exit();  // 現在の状態が存在する場合、終了処理を呼び出す
        currentState = newState;  // 新しい状態を現在の状態に設定
        currentState.Enter();  // 新しい状態の初期化処理を実行
    }

    public void Update()
    {
        currentState?.Update();  // 現在の状態のUpdateメソッドを呼び出す
    }
}

StateMachine

public class StateMachine
{
    private IState currentState; // 現在アクティブな状態を保持するフィールド

    public void ChangeState(IState newState)
    {
        currentState?.Exit();  // 現在の状態が存在する場合、終了処理を呼び出す
        currentState = newState;  // 新しい状態を現在の状態に設定
        currentState.Enter();  // 新しい状態の初期化処理を実行
    }

    public void Update()
    {
        currentState?.Update();  // 現在の状態のUpdateメソッドを呼び出す
    }
}

プレイヤー制御

public class PlayerController : MonoBehaviour
{
    private StateMachine stateMachine; // プレイヤーの状態を管理するStateMachine

    private void Start()
    {
        stateMachine = new StateMachine(); // StateMachineのインスタンスを作成
        stateMachine.ChangeState(new IdleState()); // 初期状態をIdleStateに設定
    }

    private void Update()
    {
        stateMachine.Update(); // 現在の状態のUpdateメソッドを呼び出す

        if (Input.GetKeyDown(KeyCode.Space))
        {
            stateMachine.ChangeState(new WalkState()); // スペースキーが押されたらWalkStateに変更
        }
    }
}

特徴

  • 各状態が独立したクラスで定義されるため、役割が明確になります。
  • 状態を追加しても、既存のコードに影響を与えにくいです。

状況に応じた選択

  • 簡単な実装が求められる場合: 状態が少ない場合や、簡単な状態管理を行いたい場合にはenum + switchが適しています。
  • 柔軟性と拡張性が重要な場合: 状態が多い、もしくは状態ごとに異なる複雑な処理を行う場合はクラスを用いたStateパターンを使用します。

Discussion