WisdomSoft - for your serial experiences.

コルーチンの待機

コルーチンが別のコルーチンの終了を待機したり、時間経過などを待機したりする方法を解説します。

階層化したコルーチンの終了を待つ

StartCoroutine() メソッドは、コルーチンの実行単位を表す Coroutine オブジェクトを返します。Coroutine クラス自体に特別な機能や値は無く、このオブジェクトはコルーチンの待機のために用いられます。

コルーチンが複数の階層的な処理を実行する場合、コルーチンの内部で別のコルーチンを発生させることができます。このとき、コルーチンが yield return 文の戻り値(すなわち、反復子の要素)として UnityEngine.YieldInstruction クラスを継承するオブジェクトを返すと、対象の処理が終了するのを待機してから、コルーチンの続きが呼ばれます。

UnityEngine.YieldInstruction クラス
public class YieldInstruction

このクラス自体に、独自のメンバなどはありません。YieldInstruction 及び、その派生クラスはコルーチンが返す値として利用するためのオブジェクトとして使用します。その 1 つの例が StartCoroutine() メソッドが返す Coroutine クラスです。Coroutine クラスはコルーチンの処理単位を表します。これを yield return で返すと、対象のコルーチンが終了するまで、そのコルーチンの呼び出しは待機されます。よって、コルーチン内で発生させた別のコルーチンの終了を待機させることができます。

コード1
using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(Coroutine());
    }

    IEnumerator Coroutine()
    {
        Debug.Log("Rotate: " + Vector3.up);
        yield return StartCoroutine(Rotate(3, Vector3.up));

        Debug.Log("Rotate: " + Vector3.right);
        yield return StartCoroutine(Rotate(3, Vector3.right));

        Debug.Log("Rotate: " + Vector3.forward);
        yield return StartCoroutine(Rotate(3, Vector3.forward));
    }

    IEnumerator Rotate(float sec, Vector3 rot)
    {
        var startTime = Time.time;
        while (Time.time - startTime < sec)
        {
            transform.Rotate(rot);
            yield return null;
        }
    }
}
実行結果
コード1 実行結果

コード1は Y 軸、X 軸、Z 軸の回転を 3 秒ごとに切り替えて行うスクリプトです。

Rotate() メソッドは sec パラメータに指定された秒数だけ、rot パラメータで渡されたベクトルで自身を回転させるコルーチンです。この Rotate() メソッドを、Coroutine() メソッド内で StartCoroutine() メソッド経由でコルーチンとして起動しています。このとき、StartCoroutine() メソッドが返したオブジェクトを yield return で返すことで、Rotate() メソッドのコルーチンが終了するのを待機します。こうすることで、コルーチンの内部で、別のコルーチンの終了を待つことができます。

もちろん、コルーチンは単純な反復子なので、その仕組みを知っていれば手動でコルーチンの呼び出しや待機を行うこともできます。上記のコード1の Coroutine() メソッドは、以下のように書き換えることもできます。

IEnumerator Coroutine()
{
    Debug.Log("Rotate: " + Vector3.up);
    var ye = Rotate(3, Vector3.up);
    while (ye.MoveNext()) yield return ye.Current;

    Debug.Log("Rotate: " + Vector3.right);
    var xe = Rotate(3, Vector3.right);
    while (xe.MoveNext()) yield return xe.Current;

    Debug.Log("Rotate: " + Vector3.forward);
    var ze = Rotate(3, Vector3.forward);
    while (ze.MoveNext()) yield return ze.Current;
}

IEnumerator インターフェイスの MoveNext() メソッドは、正常に次の要素を取得できた場合は true 、そうでなければ false を返します。この MoveNext() メソッドを呼び出すことでコルーチンが呼び出されるため、while 文で MoveNext() が false になるまで繰り返すことで、コルーチンの終了を監視できます。

一定時間待機する

一定時間が経過したら何かをするというような処理にコルーチンが適しているのは前述のとおりですが、コード1のように Time クラスの機能を使って時間経過を計算しなくても、Unity 標準の UnityEngine.WaitForSeconds クラスを使うことで、簡単にコードを待機できます。

UnityEngine.WaitForSeconds クラス
public sealed class WaitForSeconds : YieldInstruction

WaitForSeconds クラスは YieldInstruction を継承しているため、コルーチンの要素として返すことで指定時間だけコルーチンの実行を待機できます。待機時間は、コンストラクタから指定します。

WaitForSeconds のコンストラクタ
public WaitForSeconds(float seconds);

seconds パラメータに秒単位の待機時間を指定します。コルーチンの yield return 文で、このオブジェクトを返すことで、コルーチンを指定時間だけ待機できます。

コード2
using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private Vector3 _rot = Vector3.zero;

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

    void Update()
    {
        transform.Rotate(_rot);
    }

    IEnumerator Coroutine()
    {
        Debug.Log("Rotate: " + Vector3.up);
        _rot = Vector3.up;
        yield return new WaitForSeconds(3);

        Debug.Log("Rotate: " + Vector3.right);
        _rot = Vector3.right;
        yield return new WaitForSeconds(3);

        Debug.Log("Rotate: " + Vector3.forward);
        _rot = Vector3.forward;
        yield return new WaitForSeconds(3);

        _rot = Vector3.zero;
    }
}
実行結果
コード 実行結果

コード2コード1とは別の方法で、3秒ごとに回転軸を切り替えるスクリプトです。回転処理はコルーチンではなく Update() メソッド内に記述されており、_rot フィールドに保存されているベクトルだけ回転させます。コルーチンからは _rot フィールドを変更することで回転を制御します。

Start() メソッド内で Coroutine() メソッドが返す反復子をコルーチンとして起動しています。コルーチンでは _rot フィールドの値を更新し、どの方向に回転させるかを設定します。続いて 3 秒間待機する WaitForSeconds オブジェクトを返し、その後に次の回転処理の設定を行う、という流れになっています。一定時間が経過したら次の処理に移るという制御を WaitForSeconds オブジェクトを返すコルーチンにすることで手続的に書くことができることが確認できます。