5
4

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 5 years have passed since last update.

親のCoroutineからネストしたCoroutineを制御したい

Posted at

概要

UnityのCoroutineは基本StopCoroutine(),StopAllCoroutineAll()で一時停止する仕様になっているが、ネストしたCoroutineも含めて親のCoroutineからStopCoroutineするとネストしたCoroutineが終了してからしか一時停止しません。

実例

例えば以下のようなコードで


IEnumerator _coroutine;

void Start()
{
  _coroutine = Coroutine();
  StartCoroutine(_coroutine);
}

IEnumerator Coroutine()
{

    yield return NestCoroutine();

}

IEnumerator NestCoroutine()
{
    var time = 0f;
    while(time < 2f){
       yield return null;
       time += Time.deltaTime;
    }
}

Coroutineを実行中に以下のように親のCoroutineをStopしようとしてもNestCoroutineの終了を待ってからしかStopしてくれません。

void Stop()
{
    StopCoroutine(_coroutine);
}

これが結構不便で、例えばゲームのポーズを実装したい場合にStopAllCoroutineで全部停止して済むなら良いのですが、特定のCoroutineだけ止めたい場合もあるわけで。
こういう場合に親のCoroutineをStopCoroutineさせるだけではネストしたCoroutineが止まってくれないので、ネストした全てのCoroutine中にフラグを設定してフラグで制御するとか、すべてのネストしたCoroutineを変数に格納しておいてStopCoroutineするなど面倒な制御を要求されます。

こういった細かい制御をしたい場合IEnumeratorを自前でMoveNext()していく方法があります。
MoveNestでCoroutineを実行していく場合、以下のように書きます。UpdateでMoveNextを回して行くのが一般的ですが、あえてStartCoroutine中でMoveNextを回していきます。理由は後述。


void Start()
{
   StartCoroutine(TaskCoroutine(Coroutine()));
}

IEnumerator TaskCoroutine(IEnumerator task)
{
    while(true)
    {
        if(task.MoveNext()){
            yield return null;
        }
        else {
            break;
        }
    }
}

IEnumerator Coroutine()
{
  yield return NestCoroutine();
}

IEnumrator NestCoroutine()
{
   yield return null;
}

ただし、MoveNext()を単純に回して行くだけだとネストしたコルーチンには対応できません。
ではどうするかというと、実はネストしたコルーチンの情報はIEnumeratorのCurrentの部分に含まれているのでこれをまたMoveNext()で回して行けばよいのです。

    public interface IEnumerator
    {
        object Current { get; }

        bool MoveNext();
        void Reset();
    }
}

後は、currentの型を判断してネストしたコルーチンに何が含まれているかで場合分けして処理を分けます。typeがIEnumeratorの場合はネストしたコルーチンなので、再帰的にMoveNextを実行します。
前述したようにStartCoroutineでMoveNextを実行しているのは、IEnumerator以外の型の場合はUnity側に処理を任せるためです。

後は、唯一設定したフラグ_isPauseを制御することで、ネストしたコルーチン含めて一時停止、一時停止解除することが出来ます。


    bool _isPause = false

    void Start()
    {
      var task = Coroutine();
      StartCoroutine(TaskCoroutine(task));
    }


    IEnumerator TaskCoroutine(IEnumerator task)
    {
        while (true)
        {
            var ie = MoveNext(task);
            yield return ie;
            if (ie.Current is bool)
            {
                if (!(bool)ie.Current)
                {
                    break;
                }
            }
        }
    }

    IEnumerator MoveNext(IEnumerator task)
    {
        if (_isPause)
        {
            yield return null;

        }
        else
        {
            if (task.MoveNext())
            {
                var current = task.Current;

                if (current == null)
                {
                    yield return null;
                }
                else
                {
                    var type = current.GetType();
                    //IEnumeratorはcurrent isで判断する
                    if (current is IEnumerator)
                    {

                        while (true)
                        {
                            //currentがIEnumeratorの場合再帰的に実行
                            var ie = MoveNext((IEnumerator)current);
                            yield return ie;
                            if (ie.Current is bool)
                            {
                                if (!(bool)ie.Current)
                                {
                                    break;
                                }
                            }
                        }
                    }
                    //IEnumerator以外はcurrent.GetType()で判断する
                    //下記は説明用の分岐。
                    else if (type == typeof(Coroutine))
                    {
                        yield return current;

                    }
                    else
                    {
                        yield return current;
                    }
                }
            }
            else
            {
                yield return false;
            }
        }

    }

    IEnumerator Coroutine()
    {
        yield return NestCoroutine();
    }

    IEnumerator NestCoroutine()
    {
        var time = 0f;
        while (time < 2f)
        {
            yield return null;
            time += Time.deltaTime;
            Debug.Log(time);
        }
    }

補足

上記のコードを見ると分かりますが一時停止できるのはネストしたコルーチンがyield return (IEnumerator関数)の時だけで、yield return new WaitForSecound(1.0f)とか、yield return StartCoroutine(Coroutine()) とかの場合には無理です。てか、StopCoroutineがそもそもネストしたコルーチンも含めてStopできないのが問題なのです。

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?