はじめに
UnityでデザインパターンのState処理のような、さまざまな状態の管理と遷移をコルーチンを使用して行うクラスの紹介記事となります。
基本的なStateの使用方法にコルーチンと連携を想定したものになります。
ソースコード
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
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の解説
-
各Stateコルーチンに紐づける列挙体(enum)を用意する
private enum State { StateA, StateB, StateC, }
今回は3つのStateを使用するので3つ分を用意しました。
-
Stateを設定したCoroutineState型の変数を用意
private CoroutineState<State> _coroutineState = null;
-
初期化
_coroutineState = new CoroutineState<State>(this);
CoroutineStateクラス内でコルーチンを使用すためにMonoBehaviourを継承した自身のクラスを設定する。
-
コルーチンの用意
private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState) { //次のStateへ setNextState(State.StateB); yield break; }
Stateとして使用するコルーチンを用意します。定義するための条件としては、引数に
CoroutineState<State>.SetNextStateFunc setNextState
を定義することです。この引数setNextState
は次のステートへの行き先を設定する関数となってます。setNextStateメソッドを使用しない場合は、そこでStateが終了となります。 -
コルーチンの登録
_coroutineState.AddState(State.StateA, CoroutineStateA); _coroutineState.AddState(State.StateB, CoroutineStateB); _coroutineState.AddState(State.StateC, CoroutineStateC);
CoroutineStateクラスのAddStateメソッドを使用して、コルーチンを登録します。引数に用意した列挙体と列挙体に紐づけるコルーチンを設定します。これにより
State.StateA
でCoroutineStateA
を呼ぶことが出来ます。 -
State処理開始
_coroutineState.StartState(State.StateA);
CoroutineStateクラスのStartStateメソッドを使用して、引数に開始するステートの列挙体を指定してState処理が開始します。この場合コルーチン
CoroutineStateA
が起動します。 -
Stateの遷移と終了
//コルーチン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処理が終了します。
-
終了の検知
//State終了判定 if(_coroutineState.IsEndState) { Debug.Log("End State"); }
CoroutineStateクラスのIsEndStateプロパティを参照するとこによって終了を検知できます。
使用例2
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と同じなので、違い部分のみ解説します。
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記事