コルーチン:Update なしで時間かけて処理する
時間かけて処理する、または待ってから実行するには?
コルーチンは一時停止できるメソッドです。yield return new WaitForSeconds(1f) は 1 秒待って同じ行から再開。StartCoroutine で起動し、停止用にハンドルを保管。実行中の GameObject が無効化・破棄されると止まります。
Update は 1 フレームに 1 回実行されて終わるだけなので、時間のかかることはすべて、フレームをまたいで生き残る変数 —— タイマー、フラグ、段階 —— で組み立て直す必要があります。coroutine は同じロジックを上から下への直線として書き、途中で止めることを許可したものです。
同じ仕事を、2 通りに書く
// With Update: three fields and a branch, and the logic is scattered.
bool flashing;
float flashEnds;
void Update()
{
if (flashing && Time.time >= flashEnds)
{
mat.color = Color.white;
flashing = false;
}
}
void Hit()
{
mat.color = Color.red;
flashing = true;
flashEnds = Time.time + 0.1f;
}
// With a coroutine: the whole story reads top to bottom, in one place.
IEnumerator Flash()
{
mat.color = Color.red;
yield return new WaitForSeconds(0.1f);
mat.color = Color.white;
}
void Hit() => StartCoroutine(Flash());ルール
メソッドは IEnumerator を返し、yield return で一時停止します。yield があることで、それはただのメソッドではなく coroutine になります。
呼ぶだけでは何も起きません。Flash() だけだと coroutine を作って捨てるところまで。実際に動かすのは StartCoroutine(Flash()) です。
停止した位置と同じフレーム上の位相 —— Update の後、LateUpdate の前 —— で再開します。つまりスレッドではなく、並行して動くものは何もありません。
coroutine は起動した MonoBehaviour に属します。その GameObject を無効化するか破棄すると、coroutine は途中で完了せずに止まります。component を無効化しただけでは止まりません。
何が待てるか
yield return null; // one frame
yield return new WaitForSeconds(2f);
yield return new WaitForSecondsRealtime(2f); // two seconds of wall time
yield return new WaitUntil(() => player.isAlive);
yield return StartCoroutine(Other()); // run Other, wait for it to finish時間をかけて何かを動かす
IEnumerator MoveTo(Vector3 to, float seconds)
{
Vector3 from = transform.position;
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime / seconds;
// SmoothStep instead of a plain Lerp: starts and ends gently.
transform.position = Vector3.Lerp(from, to, Mathf.SmoothStep(0f, 1f, t));
yield return null;
}
// Land exactly on the target: the loop leaves t slightly over 1.
transform.position = to;
}止める
StopCoroutine("Flash") のように名前だけで止める方法は、名前付きで起動した場合にだけ機能します。しかもその文字列はコンパイラにチェックされません。代わりに handle を保持してください。
Coroutine c = StartCoroutine(Flash()); としておき、StopCoroutine(c) で止めます。この形なら名前を変えても壊れません。
同じ coroutine を 2 回起動すれば、2 つが走ります。点滅効果なら先に古い handle を止めておかないと、2 つが重なって色が変な場所で残ります。
使わないほうがいい場合
ずっと毎フレーム走る処理は Update に置くべきです。while (true) ループに yield return null を添えた coroutine は、ひと手間を増やしただけの Update にすぎません。
物理に関わるものは FixedUpdate、もしくは WaitForFixedUpdate を yield する coroutine に置きます。通常の coroutine が再開するのはフレーム側の時計で、物理側ではありません。
何百もの coroutine を同時に動かすとメモリを実際にくいます。1 つごとにアロケーションが起きるからです。まったく同じタイマーが数百本なら、リストを 1 つの Update で回すほうが安く済みます。
これを作るレッスン
クールダウンつきスキルシステム
この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — キャラクターと戦闘 のレッスン 04。