【Unity】コルーチンを使用したState処理

# はじめに
UnityでデザインパターンのState処理のような、さまざまな状態の管理と遷移をコルーチンを使用して行うクラスの紹介記事となります。
基本的なStateの使用方法にコルーチンと連携を想定したものになります。

# ソースコード
```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineState<T> where T : System.Enum
{
    public delegate void SetNextStateFunc(T state);
    public delegate IEnumerator CoroutineStateFunc(SetNextStateFunc setNextStateFunc);

	//State処理終了判定
    public bool IsEndState { get; private set;} = false;

    private Dictionary<T,CoroutineStateFunc> _states = new Dictionary<T,CoroutineStateFunc>();
    private MonoBehaviour _parentMonoBehaviour=null;
    private T _currentState;
    private bool _isSetNextState=false;

    public CoroutineState(MonoBehaviour monoBehaviour)
    {
        //コルーチン起動用のMonoBehaviourを設定
        _parentMonoBehaviour = monoBehaviour;
    }

    //ステート登録
    public void AddState(T state, CoroutineStateFunc process)
    {
        if (_states.ContainsKey(state)) return;
        _states.Add(state, process);
    }
	//ステート処理開始
    public Coroutine StartState(T startState)
    {
        if (_states.ContainsKey(startState) == false)
        {
            return null;
        }
        _currentState = startState;
        IsEndState = false;
        return _parentMonoBehaviour.StartCoroutine(Update());
    }

    private IEnumerator Update()
    {
        while(_states.TryGetValue(_currentState, out var runCoroutine))
        {
            _isSetNextState = false;
            yield return runCoroutine(SetNextState);
            if(_isSetNextState == false)
            {
                break;
            }
        }
        IsEndState = true;
        yield break;
    }

    private void SetNextState(T state)
    {
        _currentState = state;
        _isSetNextState = true;
    }
}
```

# 使用例1

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineStateTest : MonoBehaviour
{
    //Stateの種類
    private enum State
    {
        StateA,
        StateB,
        StateC,
    }

    private CoroutineState<State> _coroutineState = null;

    void Start()
    {
		//初期化
        _coroutineState = new CoroutineState<State>(this);

        //Stateの登録
        _coroutineState.AddState(State.StateA, CoroutineStateA);
        _coroutineState.AddState(State.StateB, CoroutineStateB);
        _coroutineState.AddState(State.StateC, CoroutineStateC);

		//State処理の開始
        _coroutineState.StartState(State.StateA);
    }

    private void Update()
    {
        if(_coroutineState == null) return;

		//State終了判定
        if(_coroutineState.IsEndState)
        {
            Debug.Log("End State");
            _coroutineState = null;
        }
    }

    //コルーチンA
    private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateA");
        yield return new WaitForSeconds(1.0f);
        //次のStateへ
        setNextState(State.StateB);
        yield break;
    }
    //コルーチンB
    private IEnumerator CoroutineStateB(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateB");
        yield return new WaitForSeconds(2.0f);
        //次のStateへ
        setNextState(State.StateC);
        yield break;
    }
    //コルーチンC
    private IEnumerator CoroutineStateC(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateC");
        yield return new WaitForSeconds(3.0f);
        //setNextStateを設定してないのでここで終了
        yield break;
    }
}
```

## 使用例1の実行結果

\>StateA
\>StateB
\>StateC
\>End State

## 使用例1の解説

1. 各Stateコルーチンに紐づける列挙体(enum)を用意する
    
    ```csharp
    private enum State
    {
        StateA,
        StateB,
        StateC,
    }
    ```
    
    今回は3つのStateを使用するので３つ分を用意しました。
    
2. Stateを設定したCoroutineState型の変数を用意
    
    ```csharp
    private CoroutineState<State> _coroutineState = null;
    ```
    
3. 初期化
    
    ```csharp
    _coroutineState = new CoroutineState<State>(this);
    ```
    
    CoroutineStateクラス内でコルーチンを使用すためにMonoBehaviourを継承した自身のクラスを設定する。
    
4. コルーチンの用意
    
    ```csharp
    private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState)
    {
    	//次のStateへ
        setNextState(State.StateB);
        yield break;
    }
    ```
    
    Stateとして使用するコルーチンを用意します。定義するための条件としては、引数に
    `CoroutineState<State>.SetNextStateFunc setNextState`
    を定義することです。この引数`setNextState`は次のステートへの行き先を設定する関数となってます。setNextStateメソッドを使用しない場合は、そこでStateが終了となります。
    
5. コルーチンの登録
    
    ```csharp
    _coroutineState.AddState(State.StateA, CoroutineStateA);
    _coroutineState.AddState(State.StateB, CoroutineStateB);
    _coroutineState.AddState(State.StateC, CoroutineStateC);
    ```
    
    CoroutineStateクラスのAddStateメソッドを使用して、コルーチンを登録します。引数に用意した列挙体と列挙体に紐づけるコルーチンを設定します。これにより`State.StateA`で`CoroutineStateA`を呼ぶことが出来ます。
    
6. State処理開始
    
    ```csharp
    _coroutineState.StartState(State.StateA);
    ```
    
    CoroutineStateクラスのStartStateメソッドを使用して、引数に開始するステートの列挙体を指定してState処理が開始します。この場合コルーチン`CoroutineStateA`が起動します。
    
7. Stateの遷移と終了
    
    ```csharp
    //コルーチンA
    private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        //次のStateへ
        setNextState(State.StateB);
        yield break;
    }
    //コルーチンB
    private IEnumerator CoroutineStateB(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        //次のStateへ
        setNextState(State.StateC);
        yield break;
    }
    //コルーチンC
    private IEnumerator CoroutineStateC(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        //setNextStateを設定してないのでここで終了
        yield break;
    }
    ```
    定義したコルーチンの引数に設定したsetNextStateメソッドに次のStateへの列挙体を設定することで、次のコルーチンが起動します。setNextStateメソッドを使用しない、または登録してない値が入った場合は、State処理が終了します。
    
8. 終了の検知
    ```csharp
    //State終了判定
    if(_coroutineState.IsEndState)
    {
        Debug.Log("End State");
    }
    ```
    
    CoroutineStateクラスのIsEndStateプロパティを参照するとこによって終了を検知できます。
    

# 使用例2

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineStateTest2 : MonoBehaviour
{
    //Stateの種類
    private enum State
    {
        StateA,
        StateB,
        StateC,
    }

    private CoroutineState<State> _coroutineState = null;

    void Start()
    {
        //初期化
        _coroutineState = new CoroutineState<State>(this);

        //Stateの登録
        _coroutineState.AddState(State.StateA, CoroutineStateA);
        _coroutineState.AddState(State.StateB, CoroutineStateB);
        _coroutineState.AddState(State.StateC, CoroutineStateC);

        //通常コルーチン起動
        StartCoroutine(MainCoroutine());
    }

    //通常コルーチン
    private IEnumerator MainCoroutine()
    {
		//State開始　State処理終了まで待機する
        yield return _coroutineState.StartState(State.StateA);

        Debug.Log("End State");
        yield break;
    }

    //コルーチンA
    private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateA");
        yield return new WaitForSeconds(1.0f);
        //次のStateへ
        setNextState(State.StateB);
        yield break;
    }
    //コルーチンB
    private IEnumerator CoroutineStateB(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateB");
        yield return new WaitForSeconds(2.0f);
        //次のStateへ
        setNextState(State.StateC);
        yield break;
    }
    //コルーチンC
    private IEnumerator CoroutineStateC(CoroutineState<State>.SetNextStateFunc setNextState)
    {
        Debug.Log("StateC");
        yield return new WaitForSeconds(3.0f);
        //setNextStateを設定してないのでここで終了
        yield break;
    }
}
```

## 使用例2の実行結果

\>StateA
\>StateB
\>StateC
\>End State

## 使用例2の解説

基本的な設定は使用例1と同じなので、違い部分のみ解説します。

```csharp
void Start()
{
    //初期化
    _coroutineState = new CoroutineState<State>(this);

    //Stateの登録
    _coroutineState.AddState(State.StateA, CoroutineStateA);
    _coroutineState.AddState(State.StateB, CoroutineStateB);
    _coroutineState.AddState(State.StateC, CoroutineStateC);

    //通常コルーチン起動
    StartCoroutine(MainCoroutine());
}

//通常コルーチン
private IEnumerator MainCoroutine()
{
	//State開始　State処理終了まで待機
    yield return _coroutineState.StartState(State.StateA);

    Debug.Log("End State");
    yield break;
}
```

CoroutineStateを起動する`StartState`メソッドを、別途用意した通常のコルーチン内で`yield retrun`を使用して呼ぶことによって、CoroutineStateが終了するまで、コルーチン内で処理を待機することが出来ます。

# おわりに
使用例1のような基本的な使用方法もありますが、使用例2のようなコルーチンと連携した使用方法もあるため、CoroutineStateからCoroutineStateを呼ぶといった使い方も可能です。なのでUnityにおいてそこそこ利便性のある機能となってます。

# 参考
Unity公式で解説されているのState記事

https://unity.com/how-to/develop-modular-flexible-codebase-state-programming-pattern