コルーチンを使うと、複数のフレームにタスクを分散させることができます。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 がチェックを行う回数を削減できます。
コルーチンを停止するには、StopCoroutine と StopAllCoroutines を使用します。また、コルーチンは、SetActive(false) を false
に設定してそのアタッチ先のゲームオブジェクトを無効にした場合にも停止します。Destroy(example)
(example
は MonoBehaviour インスタンス) を呼び出すと即座に OnDisable がトリガーされ、Unity がコルーチンを処理し、実質的にそれを停止します。最後に、OnDestroy
がフレームの最後に呼び出されます。
ノート: enabled を false
に設定することで 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 プロファイラーモジュール を使用してアプリケーションのコルーチンを調査できます。
一連の操作をできるだけ少ないコルーチンにまとめることをお勧めします。入れ子になったコルーチンは、コードの明瞭化と維持の面では便利ですが、コルーチンがオブジェクトを追跡するため、メモリオーバーヘッドが大きくなります。
ほぼ毎フレーム実行され、長時間実行される処理で yield
しないコルーチンがある場合は、これを Update
または LateUpdate
コールバックに置き換えるた方がパフォーマンスが向上します。これは特に、長時間実行されるコルーチンや無限にループするコルーチンにおいて有用です。