2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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の解説

  1. 各Stateコルーチンに紐づける列挙体(enum)を用意する

    private enum State
    {
        StateA,
        StateB,
        StateC,
    }
    

    今回は3つのStateを使用するので3つ分を用意しました。

  2. Stateを設定したCoroutineState型の変数を用意

    private CoroutineState<State> _coroutineState = null;
    
  3. 初期化

    _coroutineState = new CoroutineState<State>(this);
    

    CoroutineStateクラス内でコルーチンを使用すためにMonoBehaviourを継承した自身のクラスを設定する。

  4. コルーチンの用意

    private IEnumerator CoroutineStateA(CoroutineState<State>.SetNextStateFunc setNextState)
    {
    	//次のStateへ
        setNextState(State.StateB);
        yield break;
    }
    

    Stateとして使用するコルーチンを用意します。定義するための条件としては、引数に
    CoroutineState<State>.SetNextStateFunc setNextState
    を定義することです。この引数setNextStateは次のステートへの行き先を設定する関数となってます。setNextStateメソッドを使用しない場合は、そこでStateが終了となります。

  5. コルーチンの登録

    _coroutineState.AddState(State.StateA, CoroutineStateA);
    _coroutineState.AddState(State.StateB, CoroutineStateB);
    _coroutineState.AddState(State.StateC, CoroutineStateC);
    

    CoroutineStateクラスのAddStateメソッドを使用して、コルーチンを登録します。引数に用意した列挙体と列挙体に紐づけるコルーチンを設定します。これによりState.StateACoroutineStateAを呼ぶことが出来ます。

  6. State処理開始

    _coroutineState.StartState(State.StateA);
    

    CoroutineStateクラスのStartStateメソッドを使用して、引数に開始するステートの列挙体を指定してState処理が開始します。この場合コルーチンCoroutineStateAが起動します。

  7. 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処理が終了します。

  8. 終了の検知

    //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記事

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?