Coroutine
A Coroutine is a special method that can pause (yield return) and resume later without blocking the main thread — letting gameplay logic span many frames.
Imagine...
A Coroutine is like a bookmark in a book. You are reading (running code), hit a wait → drop a bookmark (yield return), close the book to do other work. Next frame, Unity opens the book at the bookmark and continues. Unlike async/await, coroutines stay on the main thread — not more thread-safe, but fully wired into the Unity lifecycle.
The concept in detail
A Coroutine is an IEnumerator — C# iterator protocol. Each
yield return yields a yield instruction:
null (wait 1 frame), WaitForSeconds(t),
WaitForFixedUpdate(), WaitUntil(predicate)…
StartCoroutine vs async: Coroutines run on Unity’s
main thread and can touch every Unity API. async/await can resume on another
thread (unless you keep the SynchronizationContext). Coroutines stop when the MonoBehaviour
is disabled/destroyed; async does not stop by itself.
Cache WaitForSeconds:
new WaitForSeconds(t) allocates each call. Store and reuse:
var wait = new WaitForSeconds(2f); inside a loop.
Stopping: StopCoroutine() needs the
Coroutine handle from StartCoroutine.
StopAllCoroutines() stops every coroutine on that MonoBehaviour.
Diagram: Coroutine vs Update
❌ Update approach (polling)
void Update() {
if (Time.time > startTime + 2f)
DoAction(); // check every frame!
}
⚡ Checks the condition every frame even when idle
✅ Coroutine approach
IEnumerator Delayed() {
yield return new WaitForSeconds(2f);
DoAction(); // runs once
}
✅ No CPU cost while waiting
Coroutine timeline across frames:
Hands-on steps
Define an IEnumerator
A method returning IEnumerator with at least one yield return.
Start with StartCoroutine
Coroutine co = StartCoroutine(MyCoroutine()) — keep the handle to stop later.
Cache WaitForSeconds
Create once at the top of the class and reuse in loops: _waitSec = new WaitForSeconds(0.5f).
Stop when needed
StopCoroutine(co) or, in OnDisable, StopAllCoroutines().
Interactive simulator
Pick a Coroutine type and press Start to watch it advance across frames.
Code Execution
Frame Timeline
Press StartCoroutine...
Code example
BasicBasic FadeOut effect and delayed action.
using System.Collections;
using UnityEngine;
public class UIAnimator : MonoBehaviour
{
private CanvasGroup _group;
void Awake() => _group = GetComponent<CanvasGroup>();
public void FadeOut(float duration)
{
StartCoroutine(FadeOutRoutine(duration));
}
private IEnumerator FadeOutRoutine(float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
_group.alpha = 1f - (elapsed / duration);
yield return null; // wait one frame
}
_group.alpha = 0f;
gameObject.SetActive(false);
}
public Coroutine DoAfterDelay(float delay, System.Action action)
{
return StartCoroutine(DelayedAction(delay, action));
}
private IEnumerator DelayedAction(float delay, System.Action action)
{
yield return new WaitForSeconds(delay);
action?.Invoke();
}
}Code example
AdvancedCoroutine chaining (yield return another coroutine) and WaitUntil.
using System.Collections;
using UnityEngine;
public class LevelTransition : MonoBehaviour
{
private bool _loadingDone = false;
private readonly WaitForSeconds _framePause = new(Time.fixedDeltaTime);
public void TransitionToNextLevel()
{
StartCoroutine(TransitionSequence());
}
private IEnumerator TransitionSequence()
{
// 1. Run another Coroutine and wait for it
yield return StartCoroutine(FadeToBlack(1.0f));
// 2. Load scene async
_loadingDone = false;
StartCoroutine(LoadSceneAsync());
// 3. Wait until load finishes (WaitUntil)
yield return new WaitUntil(() => _loadingDone);
// 4. Fade in the new scene
yield return StartCoroutine(FadeFromBlack(0.5f));
}
private IEnumerator LoadSceneAsync()
{
var op = UnityEngine.SceneManagement.SceneManager
.LoadSceneAsync("Level_02");
op.allowSceneActivation = false;
while (op.progress < 0.9f)
yield return null;
op.allowSceneActivation = true;
_loadingDone = true;
}
}📌 Quick recap
- ▸
yield return null= wait one frame - ▸Cache
new WaitForSeconds(t)— avoid GC every frame - ▸Coroutines stop when the GO is disabled/destroyed
- ▸
yield return StartCoroutine()to chain coroutines
⚠️ Common mistakes
❌ Forgetting the Coroutine reference
Cannot StopCoroutine precisely without a handle
✅ _co = StartCoroutine(MyRoutine())
❌ new WaitForSeconds() inside a while loop
Allocates every iteration → GC spam
✅ Cache: var w = new WaitForSeconds(0.1f);