関数を呼び出すと、値を返す前に実行が完了します。これは、関数で行なわれるすべてのアクションは 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);
}
}
これによりゲームに悪影響を与えずにチェック回数を飛躍的に減少させられます。