Update メソッドは毎フレーム処理を書くための基本的な仕組みですが、「少し動かす → 1秒待つ → 別の動きをする」のように順番のある処理を書くと、状態管理が複雑になりがちです。このページでは、Unity のコルーチンを使って、時間をまたぐ処理を手続き的に書く方法を学びます。
このページを読み終えると、以下のことができるようになります。
Update だけで時間差のある処理を書くと状態管理が必要になる理由を説明できるyield return null が次のフレームまで待つ合図であることを理解できるStartCoroutine でコルーチンを開始できるWaitForSeconds などの標準的な待機方法を説明できるたとえば、オブジェクトに次のような演出をさせたいとします。
Update で毎フレーム処理を書く場合、今どの段階なのかをフィールドに保存しておく必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using UnityEngine;
public class UpdateSequenceSample : MonoBehaviour
{
private int _state = 0; // 0=右へ移動, 1=待つ, 2=戻る, 3=完了
private float _timer = 0f;
private Vector3 _startPos;
private Vector3 _endPos;
private void Start()
{
_startPos = transform.position;
_endPos = _startPos + new Vector3(3f, 0f, 0f);
}
private void Update()
{
_timer += Time.deltaTime;
if (_state == 0)
{
float t = Mathf.Clamp01(_timer / 2f);
transform.position = Vector3.Lerp(_startPos, _endPos, t);
if (t >= 1f)
{
_state = 1;
_timer = 0f;
}
}
else if (_state == 1)
{
if (_timer >= 1f)
{
_state = 2;
_timer = 0f;
}
}
else if (_state == 2)
{
float t = Mathf.Clamp01(_timer / 2f);
transform.position = Vector3.Lerp(_endPos, _startPos, t);
if (t >= 1f)
{
_state = 3;
Debug.Log("完了");
}
}
}
}
このコードは動きます。しかし、処理が増えるほど _state の種類が増え、if の分岐も増えます。「右へ動かす」「待つ」「戻る」という流れを読みたいだけなのに、タイマーのリセットや状態番号の切り替えに意識を取られます。
コルーチンを使うと、同じ流れを次のように上から下へ書けます。
1
2
3
4
5
6
7
private IEnumerator MoveSequence()
{
yield return MoveTo(_startPos, _endPos, 2f);
yield return new WaitForSeconds(1f);
yield return MoveTo(_endPos, _startPos, 2f);
Debug.Log("完了");
}
💡 ポイント: コルーチンは「時間をまたぐ処理の続きを Unity に覚えておいてもらう」仕組みです。
Updateで自分で管理していた状態を、コルーチンの実行位置として表現できます。
コルーチンは IEnumerator を返すメソッドとして書きます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Collections;
using UnityEngine;
public class CountFramesSample : MonoBehaviour
{
private IEnumerator CountFrames()
{
Debug.Log("1フレーム目");
yield return null;
Debug.Log("2フレーム目");
yield return null;
Debug.Log("3フレーム目");
}
}
yield return null は「次のフレームまで待つ」という合図です。Unity はこの IEnumerator を持っておき、次のフレームになったら続きを進めます。
IEnumerator や yield return の仕組みそのものを詳しく知りたい場合は、補足: IEnumerator と yield return を参照してください。このページでは、Unity がそれをフレーム待機や時間待ちに使う部分へ集中します。
yield return で止まるのは、アプリ全体でも、クラス全体でもありません。止まるのは、そのコルーチンの続きだけです。他の Update や他のコンポーネントの処理は通常どおり動き続けます。
Unity の StartCoroutine を使う前に、自分で IEnumerator を毎フレーム進める簡単な仕組みを作ってみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System.Collections;
using UnityEngine;
public class SimpleCoroutineRunner : MonoBehaviour
{
private IEnumerator _routine;
private void Start()
{
_routine = SampleRoutine();
}
private void Update()
{
if (_routine == null) return;
bool hasNext = _routine.MoveNext();
if (!hasNext)
{
_routine = null;
}
}
private IEnumerator SampleRoutine()
{
Debug.Log("開始");
yield return null;
Debug.Log("1フレーム待った");
yield return null;
Debug.Log("さらに1フレーム待った");
}
}
この例では、Update が毎フレーム _routine.MoveNext() を呼んでいます。yield return null に到達するとそこで止まり、次の Update でまた続きから進みます。
ただし、この簡易版は yield return null のように「次の MoveNext まで待つ」動きだけを見せるためのものです。yield return new WaitForSeconds(1f) のような待機時間の処理や、yield return で別のコルーチンを返す処理には対応していません。
💡 ポイント: Unity の
StartCoroutineは、このようなIEnumeratorを Unity 側で管理してくれる仕組みです。実際の開発では自分で_routine.MoveNext()を呼ぶのではなく、StartCoroutineを使います。
Unity でコルーチンを実行するには、StartCoroutine を使います。
MonoBehaviour.StartCoroutine — IEnumerator を返すメソッドをコルーチンとして開始します。
書式:MonoBehaviour.StartCoroutine メソッド
1
public Coroutine StartCoroutine(IEnumerator routine);
| パラメータ | 型 | 説明 |
|---|---|---|
routine |
IEnumerator |
実行したいコルーチン |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using UnityEngine;
public class CoroutineStartSample : MonoBehaviour
{
private void Start()
{
StartCoroutine(MoveAndWait());
}
private IEnumerator MoveAndWait()
{
Debug.Log("開始");
yield return null;
Debug.Log("次のフレーム");
yield return new WaitForSeconds(1f);
Debug.Log("1秒後");
}
}
StartCoroutine(MoveAndWait()) と書くことで、Unity が MoveAndWait の続きをフレームごとに管理します。
コルーチンでは、yield return に何を返すかによって待ち方が変わります。
| 書き方 | 待ち方 |
|---|---|
yield return null; |
次のフレームまで待つ |
yield return new WaitForSeconds(1f); |
ゲーム時間で1秒待つ |
yield return new WaitForSecondsRealtime(1f); |
現実時間で1秒待つ |
yield return new WaitForEndOfFrame(); |
そのフレームの描画処理の終わりまで待つ |
yield return new WaitUntil(() => 条件); |
条件が true になるまで待つ |
yield return new WaitWhile(() => 条件); |
条件が true の間待つ |
yield return new WaitForFixedUpdate(); |
次の FixedUpdate まで待つ(物理演算フレームに合わせる) |
WaitForSeconds はゲーム時間を使うため、Time.timeScale の影響を受けます。たとえば Time.timeScale = 0 でゲームをポーズしている間は、WaitForSeconds の待ち時間も進みません。ポーズ中も現実時間で待ちたい場合は WaitForSecondsRealtime を使います。
最後に、最初に出てきた「右へ移動 → 待つ → 戻る」を StartCoroutine で書いてみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System.Collections;
using UnityEngine;
public class CoroutineMoveSample : MonoBehaviour
{
private Vector3 _startPos;
private Vector3 _endPos;
private void Start()
{
_startPos = transform.position;
_endPos = _startPos + new Vector3(3f, 0f, 0f);
StartCoroutine(MoveSequence());
}
private IEnumerator MoveSequence()
{
yield return MoveTo(_startPos, _endPos, 2f);
yield return new WaitForSeconds(1f);
yield return MoveTo(_endPos, _startPos, 2f);
Debug.Log("完了");
}
private IEnumerator MoveTo(Vector3 from, Vector3 to, float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
transform.position = Vector3.Lerp(from, to, t);
yield return null;
}
transform.position = to;
}
}
MoveSequence では、「移動する」「待つ」「戻る」という流れをそのまま上から下へ読めます。MoveTo の中では while で毎フレーム少しずつ移動し、最後に yield return null で次のフレームまで待っています。
ここで yield return MoveTo(...) と書くと、MoveTo のコルーチンが終わるまで MoveSequence の続きは待機します。yield return に IEnumerator を渡すと、Unity はその IEnumerator を内部でコルーチンとして実行し、完了してから呼び出し元の続きを進めます。
開始したコルーチンを途中で止めたい場合は StopCoroutine を使います。StartCoroutine の戻り値を Coroutine 型で受け取っておくと、後から停止できます。
1
2
3
4
5
6
7
8
9
10
11
private Coroutine _routine;
private void Start()
{
_routine = StartCoroutine(MoveAndWait());
}
private void Stop()
{
StopCoroutine(_routine);
}
入門段階ではまず「開始する」「待つ」「順番に進める」ことを優先して理解しましょう。停止やスキップの細かい制御は、実践的なチュートリアルで扱います。
StartCoroutine を呼ばずにメソッドを直接呼ぶと、コルーチンとして動きません。
1
2
3
4
5
// ❌ NG: メソッドを呼んでいるだけで、コルーチンとして開始していない
MoveAndWait();
// ✅ OK: Unity にコルーチンとして管理してもらう
StartCoroutine(MoveAndWait());
MoveAndWait() を呼ぶだけでは、IEnumerator オブジェクトが作られるだけです。Unity はその IEnumerator を管理しないため、コルーチンとしては進みません。
while ループの中に yield return null を書き忘れると、ループが1フレームで走り切ります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ NG: yield return null がないため、while が1フレームで走り切る
while (elapsed < duration)
{
elapsed += Time.deltaTime;
transform.position = Vector3.Lerp(from, to, elapsed / duration);
}
// ✅ OK: 毎フレーム少しずつ進める
while (elapsed < duration)
{
elapsed += Time.deltaTime;
transform.position = Vector3.Lerp(from, to, elapsed / duration);
yield return null;
}
yield return null を忘れると、while は同じフレームの中で最後まで実行されます。アニメーションが一瞬で終わったり、長いループで Unity が固まったりする原因になります。
Update で順番のある処理を書くと、状態番号やタイマーの管理が増えやすいyield return null は次のフレームまで待つ合図であるStartCoroutine は IEnumerator を Unity 側で管理し、フレームや時間をまたいで続きを実行するWaitForSeconds などの Wait 系クラスで待ち方を指定できる以下の問いに答えられるか確認しましょう。
Update だけで「移動 → 待機 → 移動」のような処理を書くと、どのようなフィールド管理が必要になりますか?yield return null は Unity のコルーチンでどのような意味を持ちますか?StartCoroutine(MyRoutine()); と MyRoutine(); の違いを説明してください。WaitForSeconds と WaitForSecondsRealtime の違いを説明してください。_state、経過時間を表す _timer、開始位置や終了位置などをフィールドとして管理する必要がある。StartCoroutine(MyRoutine()) は Unity にコルーチンとして管理してもらう。MyRoutine() だけでは IEnumerator が作られるだけで、自動的には進まない。WaitForSeconds は Time.timeScale の影響を受けるゲーム時間で待つ。WaitForSecondsRealtime は Time.timeScale の影響を受けない現実時間で待つ。今後追加する「イベント・コールバックと Unity」では、処理の完了や入力を別の処理へ通知する方法を学びます。