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; }
yield return new WaitForSeconds(1f);
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();
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}%";
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
| async UniTaskVoid LoadAsync() { await SceneManager.LoadSceneAsync("GameScene"); Debug.Log("场景加载完成"); }
async UniTask MoveWithUniTask() { await transform.DOMove(targetPos, 1f); await UniTask.Delay(1000); await transform.DORotate(targetRot, 0.5f); }
|
协程适合简单的延时和分步逻辑。对于复杂异步操作(网络请求、大量并发),推荐使用 UniTask。
13.6 本章小结
- 协程通过
IEnumerator 和 yield 实现跨帧执行
WaitForSeconds / WaitUntil / null 控制等待条件
- 协程适合延时、动画序列、异步加载等场景
StopAllCoroutines 管理协程生命周期
- UniTask 是更现代的异步方案(零 GC、支持 await)
练习题
- 实现协程:物体 3 秒内从 A 点移动到 B 点
- 使用
WaitUntil 实现:当玩家进入范围时才执行后续逻辑
- 实现异步加载界面:加载进度条从 0% 到 100%
- 对比协程和 UniTask 实现同一延时操作的代码差异