协程与异步编程

13.1 协程(Coroutine)基础

协程是一种在多个帧之间分散执行逻辑的方法,适合处理延时操作分步执行

启动协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Start()
{
// 启动协程
StartCoroutine(MyCoroutine());
// 或通过字符串调用(不推荐,无编译检查)
StartCoroutine("MyCoroutine");
}

IEnumerator MyCoroutine()
{
Debug.Log("协程开始");
yield return null; // 等待一帧
Debug.Log("一帧之后");
}

yield 指令

指令 等待条件
yield return null 等待下一帧
yield return new WaitForSeconds(1f) 等待指定秒数
yield return new WaitForSecondsRealtime(1f) 等待实时秒数(不受 Time.timeScale 影响)
yield return new WaitUntil(() => condition) 直到条件为 true
yield return new WaitWhile(() => condition) 直到条件为 false
yield return new WaitForEndOfFrame() 等待帧结束
yield return StartCoroutine(Other()) 等待另一个协程完成
yield break 立即退出协程

13.2 协程实战

延时执行

1
2
3
4
5
6
7
8
9
10
IEnumerator DelayedAction(float delay, System.Action action)
{
yield return new WaitForSeconds(delay);
action?.Invoke();
}

// 使用
StartCoroutine(DelayedAction(2f, () => {
Debug.Log("2 秒后执行");
}));

动画序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
IEnumerator MoveSequence()
{
// 向前移动
float elapsed = 0f;
Vector3 start = transform.position;
Vector3 target = start + Vector3.forward * 5f;

while (elapsed < 1f)
{
elapsed += Time.deltaTime;
transform.position = Vector3.Lerp(start, target, elapsed);
yield return null;
}

// 停顿 1 秒
yield return new WaitForSeconds(1f);

// 旋转 90 度
elapsed = 0f;
Quaternion startRot = transform.rotation;
Quaternion targetRot = startRot * Quaternion.Euler(0, 90, 0);

while (elapsed < 0.5f)
{
elapsed += Time.deltaTime;
transform.rotation = Quaternion.Slerp(startRot, targetRot, elapsed / 0.5f);
yield return null;
}
}

13.3 协程的管理

1
2
3
4
5
6
7
8
// 停止特定协程
StopCoroutine("MyCoroutine");

// 停止所有协程
StopAllCoroutines();

// 停止 MonoBehaviour 上所有协程
this.StopAllCoroutines();

协程生命周期

  • 协程依附于 MonoBehaviour
  • 对象被禁用时协程继续运行
  • 对象被销毁时协程自动停止
  • 挂载 OnDisable 中手动 StopAllCoroutines

13.4 异步加载场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SceneLoader : MonoBehaviour
{
[SerializeField] private Slider progressBar;
[SerializeField] private TMP_Text progressText;

public void LoadSceneAsync(string sceneName)
{
StartCoroutine(LoadSceneCoroutine(sceneName));
}

IEnumerator LoadSceneCoroutine(string sceneName)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
operation.allowSceneActivation = false;

while (!operation.isDone)
{
float progress = Mathf.Clamp01(operation.progress / 0.9f);
progressBar.value = progress;
progressText.text = $"{progress * 100:F0}%";

// 加载完成(progress 到 0.9)后等待用户确认
if (operation.progress >= 0.9f)
{
progressText.text = "按任意键继续";
if (Input.anyKeyDown)
{
operation.allowSceneActivation = true;
}
}

yield return null;
}
}
}

13.5 协程与 UniTask 的比较

对比 协程 UniTask
返回值 IEnumerator UniTask / UniTask<T>
性能 有 GC 分配(yield 指令创建对象) 零 GC(结构体实现)
异常处理 try-catch 支持有限 完整 C# async/await 支持
取消机制 StopCoroutine CancellationToken
可等待的对象 有限 任意 await
1
2
3
4
5
6
7
8
9
10
11
12
13
// UniTask 写法(需导入 UniTask 库)
async UniTaskVoid LoadAsync()
{
await SceneManager.LoadSceneAsync("GameScene");
Debug.Log("场景加载完成");
}

async UniTask MoveWithUniTask()
{
await transform.DOMove(targetPos, 1f); // DOTween + UniTask
await UniTask.Delay(1000);
await transform.DORotate(targetRot, 0.5f);
}

协程适合简单的延时和分步逻辑。对于复杂异步操作(网络请求、大量并发),推荐使用 UniTask。


13.6 本章小结

  • 协程通过 IEnumeratoryield 实现跨帧执行
  • WaitForSeconds / WaitUntil / null 控制等待条件
  • 协程适合延时、动画序列、异步加载等场景
  • StopAllCoroutines 管理协程生命周期
  • UniTask 是更现代的异步方案(零 GC、支持 await)

练习题

  1. 实现协程:物体 3 秒内从 A 点移动到 B 点
  2. 使用 WaitUntil 实现:当玩家进入范围时才执行后续逻辑
  3. 实现异步加载界面:加载进度条从 0% 到 100%
  4. 对比协程和 UniTask 实现同一延时操作的代码差异
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Unity3D开发基础教程 系列
第 13/20 篇
分享: