U
19 / 22 · 10 分
FAQ

コルーチン:Update なしで時間かけて処理する

短い答え

時間かけて処理する、または待ってから実行するには?

コルーチンは一時停止できるメソッドです。yield return new WaitForSeconds(1f) は 1 秒待って同じ行から再開。StartCoroutine で起動し、停止用にハンドルを保管。実行中の GameObject が無効化・破棄されると止まります。

Update は 1 フレームに 1 回実行されて終わるだけなので、時間のかかることはすべて、フレームをまたいで生き残る変数 —— タイマー、フラグ、段階 —— で組み立て直す必要があります。coroutine は同じロジックを上から下への直線として書き、途中で止めることを許可したものです。

同じ仕事を、2 通りに書く

Flash red for a moment, then go back
// 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 を無効化しただけでは止まりません。

何が待てるか

The four that cover almost everything
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

時間をかけて何かを動かす

The pattern behind every fade, slide and pop
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。