Version: 2023.2
言語: 日本語
イベント関数
名前空間

コルーチン

コルーチンを使うと、複数のフレームにタスクを分散させることができます。Unity では、コルーチンは、実行を一時停止して制御を Unity に戻し、次のフレームで中断した所から続行することができるメソッドです。

ほとんどの状況では、メソッドを呼び出すと、そのメソッドは、完了まで実行された後で、呼び出し元のメソッドに制御を返し、さらに任意の戻り値を返します。つまり、メソッド内で行われる全てのアクションは、1 回のフレーム更新中に行われる必要があります。

プロシージャルアニメーションや時経的なイベントのシーケンスを格納するためにメソッド呼び出しを使用したい場合には、コルーチンが使用できます。

ただし、コルーチンはスレッドではないことに注意してください。コルーチン内で実行される同期操作は、メインスレッドで実行されることに変わりありません。メインスレッドに費やされる CPU 時間を削減したい場合は、他の多くのスクリプトコードと同様に、コルーチン内でのブロック操作を避けることが重要です。Unity 内でマルチスレッドコードを使用したい場合は、C# ジョブシステム を検討してください。

HTTP 転送の待機、アセットのロードの待機、ファイル出入力の完了待ちなど、長い非同期処理を行う必要がある場合は、コルーチンの使用が最適です。

コルーチンの例

例えば、あるオブジェクトのアルファ (不透明度) の値を、見えなくなるまで徐々に下げるというタスクがあるとします。

void Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
    }
}

この例では、Fade メソッドは、期待したような効果を発揮しない可能性があります。フェードが見えるようにするには、一連のフレームにわたってフェードのアルファを低下させることで、Unity のレンダリングする中間値を表示する必要があります。しかし、このサンプルメソッドは 1 回のフレーム更新中に全体が実行されます。中間値が表示されることはなく、オブジェクトは瞬時に消えます。

この状況は、フレーム単位でフェードを実行する Update 関数にコードを追加することで回避できます。ただし、この種のタスクには、コルーチンを使用する方が便利な場合があります。

C# では、以下のようにコルーチンを宣言します。

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return null;
    }
}

コルーチンは、IEnumerator 戻り値型で宣言し、yield return ステートメントをボディのどこかに含むメソッドです。yield return null の行は、実行が一時停止し、次のフレームで再開されるポイントです。コルーチンの実行を設定するには、以下のように StartCoroutine 関数を使用する必要があります。

void Update()
{
    if (Input.GetKeyDown("f"))
    {
        StartCoroutine(Fade());
    }
}

Fade 関数内のループカウンターは、コルーチンの生存期間を通して正しい値を保持し、全ての変数やパラメーターは yield ステートメントと yield ステートメントの間で保持されます。

コルーチンの時間遅延

デフォルトでは、Unity は yield ステートメント直後のフレームでコルーチンを再開します。時間遅延を用いたい場合は、以下のように WaitForSeconds を使用します。

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return new WaitForSeconds(.1f);
    }
}

WaitForSeconds を使用して、効果を一定期間にわたって広げることができます。またこれは、Update メソッドにタスクを含める代わりに使用することもできます。Unity は Update メソッドを 1 秒間に数回呼び出すので、タスクをそれほど頻繁に繰り返す必要がない場合は、これをコルーチンに入れることで、毎フレームではなく定期的に更新されるようにできます。

例えば、以下のようなコードで、敵が近くにいることをプレイヤーに知らせるアラームをアプリケーションに含めたとします。

bool ProximityCheck()
{
    for (int i = 0; i < enemies.Length; i++)
    {
        if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
                return true;
        }
    }

    return false;
}

敵が数多く存在する場合は、この関数を毎フレーム呼び出すと著しいオーバーヘッドが生じる可能性があります。しかし、以下のようにコルーチンを使用して 1/10 秒ごとに呼び出すことも可能です。

IEnumerator DoCheck()
{
    for(;;)
    {
        if (ProximityCheck())
        {
            // Perform some action here
        }
        yield return new WaitForSeconds(.1f);
    }
}

このようにすれば、ゲームプレイに顕著な影響を与えることなく、Unity がチェックを行う回数を削減できます。

コルーチンの停止

コルーチンを停止するには、StopCoroutineStopAllCoroutines を使用します。また、コルーチンは、SetActive(false)false に設定してそのアタッチ先のゲームオブジェクトを無効にした場合にも停止します。Destroy(example) (example は MonoBehaviour インスタンス) を呼び出すと即座に OnDisable がトリガーされ、Unity がコルーチンを処理し、実質的にそれを停止します。最後に、OnDestroy がフレームの最後に呼び出されます。

ノート: enabledfalse に設定することで MonoBehaviour を無効にした場合、Unity はコルーチンを停止しません。

コルーチンの解析

コルーチンは、他のスクリプトコードからは、異なる形で実行されます。Unity では、ほとんどのスクリプトコードは、パフォーマンストレース内で、特定のコールバック呼び出しの下の 1 箇所で出現します。ただし、コルーチンの CPU コードは、トレース内で常に 2 箇所に出現します。

コルーチン内の全ての初期コード (コルーチンメソッドの開始から最初の yield ステートメントまで) は、Unity がコルーチンを開始した時にトレース内に表示されます。初期コードはほとんどの場合は StartCoroutine メソッドが呼び出された場所に表示されます。Unity コールバックが生成するコルーチン (例: IEnumerator を返す Start コールバック) は、最初にそれぞれの Unity コールバック内に表示されます。

コルーチンのその他のコード (初回の再開時点から実行完了まで) は、Unity のメインループ内の DelayedCallManager ライン内に表示されます。

これが起こる理由は、Unity がコルーチンを実行する方法にあります。C#コンパイラー は、コルーチンに対応するクラスのインスタンスを自動生成します。その上で Unity がこのオブジェクトを使用して、1 つのメソッドの複数の呼び出しにわたってコルーチンの状態を追跡します。コルーチン内のローカルスコープ変数は複数の yield呼び出しにわたって持続する必要があるため、Unity は、生成されたクラス内にローカルスコープ変数を巻き上げ、これはコルーチンの間中ヒープ上に割り当てられたままになります。また、このオブジェクトはコルーチンの内部状態の追跡も行います。つまり、yield 後にコードのどの時点でコルーチンが再開しなければならないかを記憶しています。

このため、コルーチンの開始時に掛かるメモリ負荷は、固定のオーバーヘッドコストにそのローカルスコープ変数のサイズを足したものに等しくなります。

コルーチンを開始させるコードがオブジェクトの構築と呼び出しを行い、その上で、そのコルーチンの yield 条件が満たされた時点で Unity の DelayedCallManager がそれを再度呼び出します。通常はコルーチンは他のコルーチンの外で開始されるので、これにより、その実行オーバーヘッドが yield 呼び出しと DelayedCallManager とに分割されます。

Unity のプロファイラーを使用すると、Unity がアプリケーションのどこでコルーチンを実行するかを調査し、理解することができます。これを行うには、Deep Profiling を有効にしてアプリケーションをプロファイリングします (これによりスクリプトコードの全ての部分がプロファイリングされ、全ての関数呼び出しが記録されます)。その上で、CPU Usage プロファイラーモジュール を使用してアプリケーションのコルーチンを調査できます。

DelayedCall 内のコルーチンのプロファイラーセッション
DelayedCall 内のコルーチンのプロファイラーセッション

一連の操作をできるだけ少ないコルーチンにまとめることをお勧めします。入れ子になったコルーチンは、コードの明瞭化と維持の面では便利ですが、コルーチンがオブジェクトを追跡するため、メモリオーバーヘッドが大きくなります。

ほぼ毎フレーム実行され、長時間実行される処理で yield しないコルーチンがある場合は、これを Update または LateUpdate コールバックに置き換えるた方がパフォーマンスが向上します。これは特に、長時間実行されるコルーチンや無限にループするコルーチンにおいて有用です。

イベント関数
名前空間