🎨

【Unity】デザインパターン State ゲーム管理編

2024/12/07に公開

ゲーム開発では、ゲームの状態(ステート)や振る舞いを管理することが必要になります。その際に役立つのがStateパターンです。
今回はStateパターンでのゲーム管理について、まとめています。


1. enum + switchによるシンプルな実装

状態をenumで定義し、それをswitch文で管理する方法です。状態が少ない場合や簡単な動作を記述する際に適しています。

実装例

public enum GameState
{
    MainMenu,
    Playing,
    Paused
}

public class GameManager : MonoBehaviour
{
    private GameState currentState = GameState.MainMenu;

    // 他のマネージャーを参照
    [SerializeField] private UIManager uiManager;
    [SerializeField] private AnimationManager animationManager;

    private void Start()
    {
        // 初期状態の設定
        UpdateGameState(GameState.MainMenu);
    }

    private void Update()
    {
        // 状態ごとの処理
        switch (currentState)
        {
            case GameState.MainMenu:
                Debug.Log("Main Menu");
                break;

            case GameState.Playing:
                Debug.Log("Playing");
                break;

            case GameState.Paused:
                Debug.Log("Paused");
                break;
        }

        // 状態変更の例
        if (Input.GetKeyDown(KeyCode.M))
        {
            UpdateGameState(GameState.MainMenu);
        }
    }

    // 状態を更新するメソッド
    private void UpdateGameState(GameState newState)
    {
        if (currentState == newState) return; // 同じ状態には遷移しない
        currentState = newState;

        switch (currentState)
        {
            case GameState.MainMenu:
                uiManager.ShowMainMenu();
                animationManager.PlayMainMenuAnimation();
                break;

            case GameState.Playing:
                uiManager.ShowGameplayUI();
                animationManager.PlayGameplayAnimation();
                break;

            case GameState.Paused:
                uiManager.ShowPauseMenu();
                animationManager.PlayPauseAnimation();
                break;
        }

        Debug.Log($"Game state updated to: {currentState}");
    }
}

特徴

  • シンプルで軽量。
  • 状態が増えるとswitch文が肥大化しやすい。

2. クラスによるStateパターン

各状態を独立したクラスで管理する方法です。柔軟性が高く、状態ごとの処理を明確に分けられます。

State

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

public class MainMenuState : IState
{
    private UIManager uiManager;
    private AnimationManager animationManager;

    public MainMenuState(UIManager uiManager, AnimationManager animationManager)
    {
        this.uiManager = uiManager;
        this.animationManager = animationManager;
    }

    public void Enter()
    {
        uiManager.ShowMainMenu();
        animationManager.PlayAnimation("MainMenu");
    }

    public void Update() => Debug.Log("Main Menu: Update");

    public void Exit() => Debug.Log("Main Menu: Exit");
}

StateMachine

public class StateMachine
{
    private IState currentState;

    public void ChangeState(IState newState)
    {
        currentState?.Exit(); // 現在の状態を終了
        currentState = newState;
        currentState.Enter(); // 新しい状態を開始
    }

    public void Update()
    {
        currentState?.Update(); // 現在の状態の更新処理を呼び出す
    }
}

GameManagerとAnimationManager

public class GameManager : MonoBehaviour
{
    private StateMachine stateMachine;
    private UIManager uiManager;
    private AnimationManager animationManager;

    private void Start()
    {
        uiManager = new UIManager();
        animationManager = new AnimationManager();

        // AnimationManagerに完了イベントを登録
        animationManager.OnAnimationComplete += HandleAnimationComplete;

        stateMachine = new StateMachine();
        stateMachine.ChangeState(new MainMenuState(uiManager, animationManager));
    }

    private void Update()
    {
        stateMachine.Update();
    }

    // AnimationManagerのイベントハンドラ
    private void HandleAnimationComplete()
    {
        Debug.Log("Animation completed. Transitioning to GameState.");
        stateMachine.ChangeState(new GameState());
    }
    
    private void OnDestroy()
    {
        // イベントの解除
        animationManager.OnAnimationComplete -= HandleAnimationComplete;
    }
}
public class AnimationManager
{
    // GameManagerにアニメーション完了を通知するイベント
    public event Action OnAnimationComplete;

    public void PlayAnimation()
    {
        Debug.Log("Playing animation...");
        SimulateAnimationComplete();
    }

    private async void SimulateAnimationComplete()
    {
        await Task.Delay(2000); 
        Debug.Log("Animation finished.");
        OnAnimationComplete?.Invoke(); //  GameManagerにイベントを通知
    }
}

特徴

  • 各状態の処理を個別のクラスに分けられる。
  • 状態の増加や変更に柔軟に対応可能。
  • Stateクラスが他のManagerClassを利用する場合、それらを依存性注入の形でStateに渡す。

※依存性注入:クラスが必要とする依存関係(他のクラスやオブジェクト)を外部から注入する設計手法


まとめ

Stateパターンを使うと、オブジェクトの状態やゲーム全体の状態を整理し、柔軟で保守性の高いコードを構築できます。状態ごとに独立した処理を持たせることで、コードの見通しを良くし、状態が増えた場合でも既存のコードに影響を与えにくくなります。

Discussion