関数を呼び出すと、値を返す前に実行が完了します。これは、関数で行なわれるすべてのアクションは 1 つのフレーム内で行われるということを意味します。つまり、関数呼び出しにプロシージャルアニメーションを含んだり、時間経過が必要なイベントに使用することはできません。例として、オブジェクトのアルファ (透明度) を完全に見えなくなるまでなるまで徐々に減少させるタスクを考えてみましょう。
void Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
}
}
上記のままでは、Fade 関数に期待通りの効果を得られません。フェードが視覚的に分かるようにするためには、アルファが連続したフレームに渡って減少し、中間的な値がレンダリングされることが必要です。しかし、関数は 1 つのフレーム内で完全に実行され完了します。中間値は決して表示されず、オブジェクトは即時に透明になります。
このような状況を Update 関数にコードを追加して、フレームごとにフェードするように処理することも可能です。しかし、通常、このようなタスクではコルーチンを使用するとより便利です。
コルーチンとは実行を停止して Unity へ制御を戻し、ただし続行するときは停止したところから次のフレームで実行を継続することができる関数です。C# でコルーチンは次のように宣言します。
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return null;
}
}
要するに、これは IEnumerator 型を戻り値として yield return ステートメントをボディ部分に含める、という関数の宣言です。yield return の行が実行を停止して次のフレームから実行を継続する場所です。コルーチンを実行するには StartCoroutine 関数を使用します。
void Update() {
if (Input.GetKeyDown("f")) {
StartCoroutine("Fade");
}
}
UnityScript ではもう少し簡単です。yield ステートメントを含む関数はすべてコルーチンとして解釈され IEnumerator 戻り型は明示的に宣言する必要がありません。
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield;
}
}
さらに UnityScript でコルーチンを開始する場合、通常の関数と同様に呼び出しします。
function Update() {
if (Input.GetKeyDown("f")) {
Fade();
}
}
Fade 関数のループカウンターは、コルーチンの生存期間を通して正しい値を保持します。実際、yield が発生しても、変数やパラメーターは正しく保持されます。
デフォルトでコルーチンは yield した直後のフレームで再開しますが、遅延してから再開させるには WaitForSeconds 関数を使用します。
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
UnityScript では以下の通りです。
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield WaitForSeconds(0.1);
}
}
この方法でエフェクトを一定の時間範囲で広げられますが、最適化の方法としても便利です。ゲーム中のタスクの多くは定期的に行なう必要があり、もっとも単純な方法としては Update 関数にそれらを加えてしまうことです。ただし、この関数は 1 秒間に何度も呼び出されます。タスクがそれほど頻繁に繰り返す必要がない場合、コルーチンに入れることで毎フレーム実行することなく定期的に更新することができます。ひとつの例としては、敵が近くにいることをプレイヤーに知らせるアラームです。コード例は次のとおりです。
function 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(;;) {
ProximityCheck;
yield return new WaitForSeconds(.1f);
}
}
このようにすると、ゲームにオーバーヘッドを与えずにチェック回数を大幅に削減できます。
注意: MonoBehaviour が無効の場合でも、MonoBehaviour が完全に破棄されない限りは、コルーチンは停止しません。MonoBehaviour.StopCoroutine と MonoBehaviour.StopAllCoroutines を使ってコルーチンを停止することができます。また、MonoBehaviour が破棄されるとコルーチンも停止します。