Coroutine / IEnumerator
| 対応: | C# 1.0(2002) |
|---|
Unity で処理を時間分割して実行する『Coroutine(コルーチン)』の使い方です。コルーチンとは、処理を途中で一時停止し、次のフレームで再開できる仕組みです。Unity では内部的に IEnumerator インターフェースを使って実現しています。
構文
using System.Collections;
using UnityEngine;
// IEnumerator を返すメソッドがコルーチンの本体です。
IEnumerator CoroutineName()
{
// 次のフレームまで処理を一時停止します。
yield return null;
// 指定秒数待機します。
yield return new WaitForSeconds(float seconds);
// 次の FixedUpdate まで待機します。
yield return new WaitForFixedUpdate();
// 条件が true になるまで待機します。
yield return new WaitUntil(() => condition);
}
// コルーチンを開始します。
Coroutine handle = StartCoroutine(CoroutineName());
// コルーチンを停止します。
StopCoroutine(handle);
// このゲームオブジェクトで実行中の全コルーチンを停止します。
StopAllCoroutines();
メソッド一覧
| メソッド / クラス | 概要 |
|---|---|
| StartCoroutine(IEnumerator) | コルーチンを開始します。戻り値の Coroutine を保持しておくと後で停止できます。 |
| StopCoroutine(Coroutine) | 指定したコルーチンを停止します。StartCoroutine() の戻り値を渡します。 |
| StopAllCoroutines() | このコンポーネントで実行中の全コルーチンを停止します。 |
| yield return null | 次のフレームまで処理を一時停止します。 |
| yield return new WaitForSeconds(t) | t 秒間処理を一時停止します。Time.timeScale の影響を受けます。 |
| yield return new WaitForSecondsRealtime(t) | t 秒間処理を一時停止します。Time.timeScale の影響を受けません(ポーズ中も動きます)。 |
| yield return new WaitUntil(() => 条件) | 条件が true になるまで毎フレーム待機します。 |
| yield return new WaitForFixedUpdate() | 次の FixedUpdate まで待機します。物理演算と同期する際に使います。 |
サンプルコード
カウントダウンと点滅を並行して実行するコルーチンのサンプルです。
CoroutineSample.cs
using System.Collections;
using UnityEngine;
public class CoroutineSample : MonoBehaviour
{
private Coroutine blinkCoroutine;
void Start()
{
// カウントダウンコルーチンを開始します。
StartCoroutine(CountDownCoroutine(5));
// 戻り値を保持して後から止められるようにします。
blinkCoroutine = StartCoroutine(BlinkCoroutine(0.5f));
// 3秒後に点滅を止めます。
StartCoroutine(DelayedAction(3f, () =>
{
StopCoroutine(blinkCoroutine);
Debug.Log("点滅停止");
}));
}
// カウントダウンするコルーチンです。
IEnumerator CountDownCoroutine(int seconds)
{
for (int i = seconds; i > 0; i--)
{
Debug.Log($"カウントダウン: {i}");
yield return new WaitForSeconds(1f);
}
Debug.Log("スタート!");
}
// オブジェクトを点滅させるコルーチンです。
IEnumerator BlinkCoroutine(float interval)
{
Renderer rend = GetComponent<Renderer>();
while (true)
{
if (rend != null)
rend.enabled = !rend.enabled;
yield return new WaitForSeconds(interval);
}
}
// 指定秒数後にアクションを実行するコルーチンです。
IEnumerator DelayedAction(float delay, System.Action action)
{
yield return new WaitForSeconds(delay);
action?.Invoke();
}
}
実践パターン: WaitUntil で条件待機
ゲームの状態が変わるまで待機するパターンです。『WaitUntil』を使うことで、フレームごとのポーリングを簡潔に書けます。
EnemySpawner.cs
using System.Collections;
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
private bool playerReady = false;
private int waveCount = 0;
void Start()
{
StartCoroutine(SpawnWaveCoroutine());
}
IEnumerator SpawnWaveCoroutine()
{
// playerReady が true になるまで毎フレーム待機します。
yield return new WaitUntil(() => playerReady);
waveCount++;
Debug.Log($"第 {waveCount} ウェーブ開始");
// 敵をスポーンして 30 秒待機します。
SpawnEnemies();
yield return new WaitForSeconds(30f);
Debug.Log($"第 {waveCount} ウェーブ終了");
// 次のウェーブを開始します。
StartCoroutine(SpawnWaveCoroutine());
}
void SpawnEnemies()
{
Debug.Log("敵をスポーンしました");
}
// 外部からプレイヤーの準備完了を通知します。
public void SetPlayerReady()
{
playerReady = true;
}
}
実践パターン: ネストしたコルーチン
コルーチン内から別のコルーチンを『yield return StartCoroutine()』で呼ぶと、内側のコルーチンが完了するまで待ってから次の処理に進みます。
SequenceCoroutine.cs
using System.Collections;
using UnityEngine;
public class SequenceCoroutine : MonoBehaviour
{
void Start()
{
StartCoroutine(MainSequence());
}
IEnumerator MainSequence()
{
Debug.Log("フェーズ1 開始");
yield return StartCoroutine(PhaseOne()); // PhaseOne 完了を待ちます。
Debug.Log("フェーズ2 開始");
yield return StartCoroutine(PhaseTwo()); // PhaseTwo 完了を待ちます。
Debug.Log("全フェーズ完了");
}
IEnumerator PhaseOne()
{
yield return new WaitForSeconds(1f);
Debug.Log("フェーズ1 完了");
}
IEnumerator PhaseTwo()
{
yield return new WaitForSeconds(2f);
Debug.Log("フェーズ2 完了");
}
}
よくあるミス
よくあるミス1: コルーチンが停止しない
『StopCoroutine()』でコルーチンを停止するには、StartCoroutine の戻り値を保持しておく必要があります。メソッド名(文字列)で StartCoroutine した場合は StopCoroutine にも文字列を渡します。
StopCoroutineNG.cs
using System.Collections;
using UnityEngine;
public class StopCoroutineNG : MonoBehaviour
{
private Coroutine handle;
void Start()
{
// NG: 戻り値を破棄してしまうと後から止められません。
StartCoroutine(BlinkCoroutine(0.5f));
// handle を保持していないため StopCoroutine(handle) が使えません。
}
// OK: 戻り値を変数に保持します。
void StartBlink()
{
handle = StartCoroutine(BlinkCoroutine(0.5f));
}
void StopBlink()
{
if (handle != null)
StopCoroutine(handle);
}
IEnumerator BlinkCoroutine(float interval)
{
while (true)
{
yield return new WaitForSeconds(interval);
}
}
}
よくあるミス2: 非アクティブなオブジェクトでのコルーチン
コルーチンは『MonoBehaviour』が有効(アクティブ)な間だけ動作します。ゲームオブジェクトが無効化(SetActive(false))されるとコルーチンは自動的に停止し、再有効化しても再開されません。
using System.Collections;
using UnityEngine;
public class InactiveCoroutineNG : MonoBehaviour
{
void Start()
{
StartCoroutine(TimerCoroutine());
// NG: SetActive(false) するとコルーチンが止まります。
// 再び SetActive(true) しても再開されません。
gameObject.SetActive(false);
// OK: コルーチンを止めてからオブジェクトを無効化します。
// StopAllCoroutines();
// gameObject.SetActive(false);
}
IEnumerator TimerCoroutine()
{
float elapsed = 0f;
while (true)
{
elapsed += Time.deltaTime;
Debug.Log($"経過: {elapsed:F1}秒");
yield return null;
}
}
}
概要
コルーチンはメインスレッドで動くため、スレッドセーフティの問題がなく Unity API を自由に呼び出せます。長時間の非同期処理(HTTP 通信など)には C# の async / await も利用できます(Unity 2017 以降)。コルーチンと async / await は共存可能です。
Unity の数学関数についてはMathfを、ベクトル演算についてはVector2 / Vector3をご確認ください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。