Unity公式が公開しているプログラミングパターンを読み取ってみた！～その10. State～

# はじめに
Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います！
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい！
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、10個目のStateについてです。

https://github.com/Unity-Technologies/game-programming-patterns-demo

https://resources.unity.com/games/level-up-your-code-with-game-programming-patterns

# 環境
OS : Windows10
Unity バージョン : 2021.3.8f1

# Stateパターンとは
Stateパターンとは、「状態」クラスとして保持し、その「状態」を入れ替えることによりコードを単純化するデザインパターンです。「状態」をif文やswitch文で分岐させることも可能ですが、
- コードが複雑化しやすい
- 「状態」が追加されるたびに、if文、switch文を見直さないといけない

という欠点があることから、よく用いられるデザインパターンになります。

# デモ
デモでは次のような挙動になっています。
![ProgrammingPatternsDemo_-_State_-_Windows__Mac__Linux_-_Unity_2021_3_8f1_Personal__DX11__2023-02-25_17-09-52_Trim_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368729/dcf77021-9426-3944-350c-2a0f9678b570.gif)
WASDキーでプレイヤーの移動、スペースキーでプレイヤーがジャンプするようになっています。
扱っている「状態」は、
- IdleState
- WalkState
- JumpState

の3つで、その「状態」がプレイヤーの頭上に表示されるようになっています。

プレイヤーには、入力値を受け取ってプレイヤーを動かしたり、自身のStateを表す変数などが実装されています。その自身のStateを表すクラスがStateMachineで、Stateの管理を行っているクラスになります。

```C#
public class PlayerController : MonoBehaviour
{
    // 入力値を受け取る変数
    [SerializeField] private PlayerInput playerInput;
    // 着地しているかどうか
    [SerializeField] private bool isGrounded = true;
    // State管理クラス変数
    private StateMachine playerStateMachine;

    public bool IsGrounded => isGrounded;
    public StateMachine PlayerStateMachine => playerStateMachine;

    private void Awake()
    {
        playerInput = GetComponent<PlayerInput>();
        playerStateMachine = new StateMachine(this);
    }

    private void Start()
    {
        // 初期値はIdleStateに設定
        playerStateMachine.Initialize(playerStateMachine.idleState);
    }

    private void Update()
    {
        // State管理クラスのUpdateを走らせる
        playerStateMachine.Update();
    }

    // プレイヤーを動かすメソッドなどが書かれている(省略)
}
```
Stateの管理を行うStateMachineクラスは、Stateを入れ替えて各Stateクラスで実装されているメソッドを走らせるようにしています。
```C#
public class StateMachine
{
    // 現状のState
    public IState CurrentState { get; private set; }

    public WalkState walkState;
    public JumpState jumpState;
    public IdleState idleState;
    // State変更時に着火するAction
    public event Action<IState> stateChanged;

    public StateMachine(PlayerController player)
    {
        this.walkState = new WalkState(player);
        this.jumpState = new JumpState(player);
        this.idleState = new IdleState(player);
    }

    public void Initialize(IState state)
    {
        // 現状のStateを変更
        CurrentState = state;
        // 現状のStateのEnterメソッドに入る
        state.Enter();
        // stateChangedに登録してあるメソッドが走る
        stateChanged?.Invoke(state);
    }

    public void TransitionTo(IState nextState)
    {
        // 現状のStateのExitメソッドに入る
        CurrentState.Exit();
        // 現状のStateをnextStateに変更
        CurrentState = nextState;
        // nextStateのEnterメソッドに入る
        nextState.Enter();
        // stateChangedに登録してあるメソッドが走る
        stateChanged?.Invoke(nextState);
    }

    public void Update()
    {
        if (CurrentState != null)
        {
            // 現状のStateのUpdateが走る
            CurrentState.Update();
        }
    }
}
```
各StateクラスはIStateインターフェースを実装していて、StateMachineで管理している現状のStateはIState型になっています。
```C#
public interface IState: IColorable
{
    public void Enter();

    public void Update();

    public void Exit();
}

public class IdleState : IState
{
    private PlayerController player;

    public IdleState(PlayerController player)
    {
        this.player = player;
    }

    public void Enter()
    {
        // 省略
    }

    public void Update()
    {
        if (!player.IsGrounded)
        {
            // if文の条件式を満たしたときStateを変更
            player.PlayerStateMachine.TransitionTo(player.PlayerStateMachine.jumpState);
        }

        if (Mathf.Abs(player.CharController.velocity.x) > 0.1f || Mathf.Abs(player.CharController.velocity.z) > 0.1f)
        {
            // if文の条件式を満たしたときStateを変更
            player.PlayerStateMachine.TransitionTo(player.PlayerStateMachine.walkState);
        }
    }

    public void Exit()
    {
        // 省略
    }
}
```
記載したのはIdleStateだけですが、WalkStateとJumpStateも同じ造りになっています。

# まとめ
Stateパターンは、「状態」クラスとして保持し、その「状態」を入れ替えることによりコードを単純化するデザインパターンです。面倒なif文やswitch文を書かなくてよいので、SOLID原則に従いやすくなります。ただし、状態の数がかなり少ないときはあまり効果を実感できないと思いますので、状態が多く複雑になる場合に使用すると良いと思います。