Unity3d技巧之 协程(协同程序)
首页 / 技术教程

Unity3d技巧之 协程(协同程序)

在 Unity 中管理时间依赖的行为,Time.deltaTime 结合 Update() 循环是标准方法。然而,在某些场景下,​协程 (Coroutine)​​ 能提供更优雅、更可控的替代方案,特别是当你想独立逻辑对每帧 Time.deltaTime 的依赖时。

核心对比:Update() + Time.deltaTime vs. 协程

  1. Update() + Time.deltaTime 模式:​

    • 原理:​​ Unity 引擎每帧会调用一次 Update()Time.deltaTime 提供了上一帧完成所需的时间(以秒为单位)。开发者通常在 Update() 内部使用 Time.deltaTime 乘以一个速度变量(如 speed)来确保运动、动画或其他时间依赖的计算“与帧率无关”,即以恒定的速率进行,无论设备的帧率高低。
    • 示例:​​ 移动物体每秒移动 5 个单位。

      public float speed = 5f;
      
      void Update() {
          transform.Translate(Vector3.forward * speed * Time.deltaTime);
      }
    • 优点:​​ 直观,适用于大多数需要逐帧更新的连续行为。
    • 缺点:​

      • 代码逻辑分散在每一帧:​​ 对于需要计时、等待或分步执行的操作,逻辑被分割在多个帧的 Update 调用中,可读性有时较差。
      • 状态管理复杂:​​ 需要在类中定义额外的成员变量(如 elapsedTime, isMoving 等)来跟踪计时状态。
      • 不够“时间驱动”:​​ 本质上还是由帧率驱动,计时精度依赖于帧率稳定性。
  2. 协程模式:​

    • 原理:​​ 协程是一种特殊的函数,使用 IEnumerator 作为返回类型。它可以在执行过程中通过 yield return 语句暂停自身的执行(非阻塞主线程),并在满足特定条件如下一帧、几秒后、协程结束等后由 Unity 引擎在恰当的时机恢复执行,继续从暂停点运行。​yield return 一个表示“等待”的对象是关键。​​
    • 核心:yield return new WaitForSeconds(time);​ 这是替代基于帧的 Time.deltaTime 累计计时的核心指令。它直接让协程暂停 time 秒,之后继续执行后续代码。
    • 优点:​

      • 基于实际时间等待:​WaitForSeconds 内部基于 Unity 的实时时钟,暂停的时间是实际的秒数,不依赖帧率(除非设置了 Time.timeScale,有对应的 WaitForSecondsRealtime)。实现了真正的“时间驱动”。
      • 状态内聚:​​ 整个时间依赖的操作逻辑(包括初始化、等待、循环、结束)可以清晰地写在一个协程函数体内,状态变量作为局部变量即可,无需分散存储。代码可读性和维护性大大提高。
      • 简化复杂序列:​​ 轻松实现如“等待 A 秒 -> 执行动作 B -> 等待 C 秒 -> 执行动作 D”这类按时间点或顺序执行的操作,无需在 Update 中维护复杂的计时器和状态标志。
      • 更好的控制:​​ 可以使用 StopCoroutine 精准停止特定的耗时操作,而无需额外的标志位。

如何使用

// 开始协程
public Coroutine StartCoroutine(IEnumerator routine);
public StartCoroutine (string methodName, object value = null);
// 停止协程
public StopCoroutine(string methodName);
public StopAllCoroutine();
// 停止协程: gameObject的active为false 或 gameObject 被销毁时
指令描述实现
WaitForSeconds等待指定秒数yield return new WaitForSeconds(10f);
WaitForFixedUpdate等待固定帧yield return new WaitForFixedUpdate();
WaitForEndOfFrame等待帧结束yield return new WaitForEndOfFrame();
StartCoroutine等待新协程结束yield return StartCoroutine(coroutine);
WaitForSecondsRealtime等待不受影响指定秒数yield return new WaitForSecondsRealtime(10f);

​1. 延迟执行​

// 传统 Update 方式
private bool hasDelayedAction = false;
private float delayTimer = 0f;
public float delayTime = 2f;

void Update() {
    if (!hasDelayedAction) {
        delayTimer += Time.deltaTime;
        if (delayTimer >= delayTime) {
            DoSomething(); // 要做的事情
            hasDelayedAction = true;
        }
    }
}
// 协程方式
void Start() {
    StartCoroutine(DelayedActionCoroutine());
}

IEnumerator DelayedActionCoroutine() {
    yield return new WaitForSeconds(delayTime); // 等待指定秒数
    DoSomething(); // 要做的事情
}

​2. 定时器/间隔执行​

// 传统 Update 方式(1秒)
private float timer = 0f;
public float interval = 1f;

void Update() {
    timer += Time.deltaTime;
    if (timer >= interval) {
        DoSomething(); // 要做的事情
        timer = 0f; // 重置计时器
    }
}
// 协程方式
void Start() {
    StartCoroutine(IntervalActionCoroutine());
}

IEnumerator IntervalActionCoroutine() {
    while (true) {
        yield return new WaitForSeconds(interval); // 等待间隔时间
        DoSomething(); // 要做的事情
    }
}
// 停止协程
StopCoroutine("IntervalActionCoroutine");

​3. ​参数化调用

void Start() {
    var value = "你好Unity!";
    // 使用object传参
    StartCoroutine("PrintHello",value);
    // 使用方法参数传参
    StartCoroutine(PrintHello(value));
}

IEnumerator PrintHello(string value) {
    yield return new WaitForSeconds(10f);
    Debug.Log(value); 
    Destroy(gameObject);
}

重要注意事项与选择建议

  1. 协程不是万能的:​Update() + Time.deltaTime需要严格每帧更新的场景(如物理模拟 FixedUpdate、非常高频的游戏逻辑、需要极低延迟的输入响应)中仍是标准且最合适的方法。协程无法在这些场景中完全替代。
  2. yield return null vs yield return new WaitForEndOfFrame():​yield return null 在 ​Update 之后、LateUpdate 之前恢复执行。yield return new WaitForEndOfFrame() 在所有相机渲染完成后、屏幕显示前恢复执行。
  3. Time.timeScale 的影响:​WaitForSecondsTime.timeScale 影响。如果设置了 Time.timeScale = 0(暂停游戏),那么 WaitForSeconds 也会暂停计时。需要不受游戏时间影响,请使用 WaitForSecondsRealtime
  4. 性能开销:​​ 启动和管理协程有一定的开销。避免在短时间内创建和销毁成千上万个协程。对于极其频繁的小任务,Update 可能更高效。
  5. 停止协程:​​ 使用 StopCoroutine(methodName)StopCoroutine(coroutineReference) 来精确停止不需要继续的协程。在对象禁用或销毁时,其启动的协程也会自动停止。

总结

Unity 协程通过 yield return new WaitForSeconds(time) 等指令,提供了一种基于实际时间而非帧计数来控制延迟、间隔执行以及简化时间依赖流程的强大机制。它的核心优势在于代码逻辑的内聚性、可读性以及对“时间点”而非“帧增量累加”的更清晰表达

edit 发表评论

您的邮箱地址不会被公开,必填项已用 * 标注