Unity Term Book
脚本与生命周期

Coroutine

Coroutine 是 Unity 中可暂停(yield return)并稍后恢复的特殊方法,不会阻塞主线程,从而让玩法逻辑跨越许多帧执行。适合延时、淡入淡出、分帧加载与回合流程;理解与 Update、async/await 的取舍,能帮程序与玩法设计师写出清晰、可停止且不易造成内存泄漏的异步逻辑。

想象一下...

Coroutine 就像书里的书签。你在阅读(运行代码),遇到等待 → 放下书签(yield return),合上书去做别的事。下一帧,Unity 从书签处打开书继续。与 async/await 不同,协程始终在主线程——并非更线程安全,但完全接入 Unity 生命周期。

概念详解

Coroutine 是 IEnumerator——C# 迭代器协议。每次 yield return 交出一条 yield instructionnull(等 1 帧)、WaitForSeconds(t)WaitForFixedUpdate()WaitUntil(predicate)

StartCoroutine 与 async:Coroutine 在 Unity 主线程上运行, 可以调用所有 Unity API。async/await 可能在其他线程恢复(除非你保留 SynchronizationContext)。 MonoBehaviour 被禁用/销毁时 Coroutine 会停止;async 不会自行停止。

缓存 WaitForSeconds: new WaitForSeconds(t) 每次调用都会分配。请存储并复用: var wait = new WaitForSeconds(2f); 放在循环内复用。

停止:StopCoroutine() 需要 StartCoroutine 返回的 Coroutine 句柄。 StopAllCoroutines() 停止该 MonoBehaviour 上的所有协程。

示意图:Coroutine 与 Update

❌ Update approach (polling)

void Update() {

if (Time.time > startTime + 2f)

DoAction(); // 每帧检查!

}

⚡ 即使空闲也每帧检查条件

✅ Coroutine approach

IEnumerator Delayed() {

yield return new WaitForSeconds(2f);

DoAction(); // 只运行一次

}

✅ 等待时不占用 CPU

Coroutine 跨多帧时间线:

Start()
→ StartCoroutine
F1: 运行到 yield
⏸ pause
F2,F3: 等待...
F4: 恢复 → 完成

动手步骤

1

定义 IEnumerator

返回 IEnumerator 的方法,且至少有一个 yield return

2

用 StartCoroutine 启动

Coroutine co = StartCoroutine(MyCoroutine()) —— 保留句柄以便稍后停止。

3

缓存 WaitForSeconds

在类顶部创建一次,在循环中复用:_waitSec = new WaitForSeconds(0.5f)

4

需要时停止

StopCoroutine(co),或在 OnDisable 中用 StopAllCoroutines()

交互模拟器

选择一种 Coroutine 类型并按 Start,观察它跨帧推进。

Code Execution

Frame Timeline

点击 StartCoroutine...

Frame: 0|State: Idle|选择 Coroutine 类型并点击 Start

代码示例

基础

基础 FadeOut 效果与延迟动作。

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();
  }
}

代码示例

进阶

Coroutine 链式调用(yield return 另一个协程)与 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;
  }
}

📌 快速记忆

  • yield return null = 等待一帧
  • 缓存 new WaitForSeconds(t) —— 避免每帧 GC
  • GO 被禁用/销毁时 Coroutine 会停止
  • yield return StartCoroutine() 可串联协程

⚠️ 常见错误

  • ❌ 忘记保存 Coroutine 引用

    没有句柄就无法精确 StopCoroutine

    ✅ _co = StartCoroutine(MyRoutine())

  • ❌ 在 while 循环里 new WaitForSeconds()

    每次迭代都分配 → GC 刷屏

    ✅ 缓存:var w = new WaitForSeconds(0.1f);