Coroutines
A coroutine is a method that can pause and continue later. Use one when something should happen over time or after a delay: fading the screen, a countdown, spawning waves of enemies.
The problem coroutines solve
A normal method runs from top to bottom in a single frame. This does not work:
void Explode()
{
Debug.Log("3...");
// There is no "wait 1 second" here. Everything below happens instantly
// (and Thread.Sleep would freeze the entire game).
Debug.Log("2...");
Debug.Log("1...");
Debug.Log("Boom!");
}
With a coroutine:
using System.Collections;
using UnityEngine;
public class Bomb : MonoBehaviour
{
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
for (int i = 3; i > 0; i--)
{
Debug.Log(i + "...");
yield return new WaitForSeconds(1f); // pause here for 1 second
}
Debug.Log("Boom!");
Destroy(gameObject);
}
}
Countdown() example over time. Each piece of the coroutine (highlighted) runs inside an ordinary frame; between pieces it is parked while Update and rendering carry on. The first piece runs inside Start, before the first Update. Later pieces resume after Update.How to write one
Reading a book with a bookmark. A normal method is reading a whole book in one sitting: nobody else gets the room until you finish. A coroutine reads a few pages, puts in a bookmark (yield return), and gives the room back to Unity so the game can keep running. Later, Unity hands you the book again and you continue from the bookmark, remembering exactly where you were. WaitForSeconds(2) is saying "hand it back to me in two seconds".
- Add
using System.Collections;at the top. - Make the method return
IEnumerator. - Put
yield return ...wherever you want to pause. - Start it with
StartCoroutine(MyMethod()). CallingMyMethod()directly does nothing.
What you can yield
| Yield this | Continues |
|---|---|
yield return null; | Next frame (after Update) |
yield return new WaitForSeconds(2f); | After 2 seconds of game time. Affected by Time.timeScale, so it never finishes while paused with timeScale 0. |
yield return new WaitForSecondsRealtime(2f); | After 2 real seconds, even when the game is paused |
yield return new WaitForFixedUpdate(); | After the next physics step |
yield return new WaitForEndOfFrame(); | After the frame is rendered (for screenshots) |
yield return new WaitUntil(() => isReady); | When the condition becomes true (checked every frame) |
yield return new WaitWhile(() => isBusy); | When the condition becomes false |
yield return StartCoroutine(Other()); | When the other coroutine has finished |
yield break; | Never. Ends the coroutine right away (like return). |
What happens at runtime
StartCoroutineruns your method immediately, up to the firstyield return.- Unity stores where it stopped and what it's waiting for.
- Every frame, Unity checks whether the wait is over. For
yield return nullandWaitForSecondsthis check happens right after allUpdatecalls. - When the wait is over, the method continues from the exact line where it paused, with all local variables intact.
- This is not multithreading. Everything still runs on the main thread, one piece per frame. A slow loop without a
yieldwill still freeze the game.
Example: fade an object out over time
using System.Collections;
using UnityEngine;
public class FadeOut : MonoBehaviour
{
[SerializeField] private float duration = 1.5f;
public void StartFade()
{
StartCoroutine(Fade());
}
IEnumerator Fade()
{
// The material's shader must support transparency for alpha to have an effect.
Material mat = GetComponent<Renderer>().material;
Color c = mat.color;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
c.a = Mathf.Lerp(1f, 0f, t / duration);
mat.color = c;
yield return null; // wait one frame, then loop again
}
gameObject.SetActive(false);
}
}
Example: spawn waves forever
using System.Collections;
using UnityEngine;
public class WaveSpawner : MonoBehaviour
{
[SerializeField] private GameObject enemyPrefab;
[SerializeField] private float delayBetweenWaves = 5f;
private Coroutine spawnRoutine;
private readonly WaitForSeconds shortWait = new WaitForSeconds(0.5f); // reuse to avoid garbage
void OnEnable() { spawnRoutine = StartCoroutine(SpawnLoop()); }
void OnDisable() { if (spawnRoutine != null) StopCoroutine(spawnRoutine); }
IEnumerator SpawnLoop()
{
int wave = 1;
while (true) // fine inside a coroutine because we yield inside the loop
{
for (int i = 0; i < wave * 3; i++)
{
Instantiate(enemyPrefab, transform.position, Quaternion.identity);
yield return shortWait;
}
wave++;
yield return new WaitForSeconds(delayBetweenWaves);
}
}
}
Stopping coroutines
StopCoroutine(handle): stop one. Keep theCoroutinereturned byStartCoroutineto use as the handle.StopAllCoroutines(): stop every coroutine started by this script.- Coroutines stop automatically when the GameObject is deactivated or destroyed. They do not restart when it's reactivated.
- Disabling only the script (
enabled = false) does not stop its coroutines.
Common Mistakes
- Calling the method without StartCoroutine.
Countdown();compiles but does nothing. - Starting a coroutine every frame in Update. You end up with hundreds running at once. Start it once, or use a flag to check if it's already running.
while(true)without a yield inside. Freezes Unity. Every loop in a coroutine needs ayieldsomewhere inside it.- Starting a coroutine on an inactive GameObject. This throws an error. The object must be active.
- Pausing with timeScale 0 and using WaitForSeconds in a pause menu. Use
WaitForSecondsRealtimeinstead.
async/await using the Awaitable class, for example await Awaitable.WaitForSecondsAsync(1f);. Coroutines are still fully supported and are the most common approach in tutorials.See also
- MonoBehaviour (StartCoroutine, Invoke)
- Iterators & yield (how coroutines work in C#)
- async & await
- Time
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Unity documentation.