Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@UnagiHuman

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

More than 3 years have passed since last update.

概要

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できないのが問題なのです。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?