在 Unity 中管理时间依赖的行为,Time.deltaTime 结合 Update() 循环是标准方法。然而,在某些场景下,协程 (Coroutine) 能提供更优雅、更可控的替代方案,特别是当你想独立逻辑对每帧 Time.deltaTime 的依赖时。
核心对比:Update() + Time.deltaTime vs. 协程
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等)来跟踪计时状态。 - 不够“时间驱动”: 本质上还是由帧率驱动,计时精度依赖于帧率稳定性。
- 代码逻辑分散在每一帧: 对于需要计时、等待或分步执行的操作,逻辑被分割在多个帧的
- 原理: Unity 引擎每帧会调用一次
协程模式:
- 原理: 协程是一种特殊的函数,使用
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);
}重要注意事项与选择建议
- 协程不是万能的:
Update()+Time.deltaTime在需要严格每帧更新的场景(如物理模拟FixedUpdate、非常高频的游戏逻辑、需要极低延迟的输入响应)中仍是标准且最合适的方法。协程无法在这些场景中完全替代。 -
yield return nullvsyield return new WaitForEndOfFrame():yield return null在 Update之后、LateUpdate之前恢复执行。yield return new WaitForEndOfFrame()在所有相机渲染完成后、屏幕显示前恢复执行。 -
Time.timeScale的影响:WaitForSeconds受Time.timeScale影响。如果设置了Time.timeScale = 0(暂停游戏),那么WaitForSeconds也会暂停计时。需要不受游戏时间影响,请使用WaitForSecondsRealtime。 - 性能开销: 启动和管理协程有一定的开销。避免在短时间内创建和销毁成千上万个协程。对于极其频繁的小任务,
Update可能更高效。 - 停止协程: 使用
StopCoroutine(methodName)或StopCoroutine(coroutineReference)来精确停止不需要继续的协程。在对象禁用或销毁时,其启动的协程也会自动停止。
总结
Unity 协程通过 yield return new WaitForSeconds(time) 等指令,提供了一种基于实际时间而非帧计数来控制延迟、间隔执行以及简化时间依赖流程的强大机制。它的核心优势在于代码逻辑的内聚性、可读性以及对“时间点”而非“帧增量累加”的更清晰表达。
最新评论