这篇按“等待、延迟、倒计时、异步加载”来查:用协程还是 async,怎么停止,暂停时计时器是否继续。
快速索引
| 我想做什么 |
推荐写法 |
| 等一帧 |
yield return null |
| 等几秒,受暂停影响 |
WaitForSeconds |
| 等几秒,不受暂停影响 |
WaitForSecondsRealtime |
| 等条件成立 |
WaitUntil |
| 停止指定协程 |
保存 Coroutine 句柄 |
| 简单延迟调用 |
Invoke |
| 重复调用 |
InvokeRepeating |
| 文件、网络、资源异步 |
async/await |
| 取消 async |
CancellationTokenSource |
| 冷却倒计时 |
Time.deltaTime |
| 真实时间倒计时 |
Time.unscaledDeltaTime |
0. 协程基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private Coroutine fadeCoroutine;
public void PlayFade() { if (fadeCoroutine != null) { StopCoroutine(fadeCoroutine); }
fadeCoroutine = StartCoroutine(FadeRoutine()); }
private IEnumerator FadeRoutine() { yield return null; yield return new WaitForSeconds(1f); fadeCoroutine = null; }
|
1. 常用 yield
1 2 3 4 5 6 7
| yield return null; yield return new WaitForSeconds(1f); yield return new WaitForSecondsRealtime(1f); yield return new WaitUntil(() => isReady); yield return new WaitWhile(() => isLoading); yield return new WaitForEndOfFrame(); yield return new WaitForFixedUpdate();
|
2. 停止协程
停止指定协程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private Coroutine routine;
private void StartWork() { routine = StartCoroutine(WorkRoutine()); }
private void StopWork() { if (routine != null) { StopCoroutine(routine); routine = null; } }
|
禁用时清理:
1 2 3 4
| private void OnDisable() { StopWork(); }
|
停止全部:
3. Invoke / InvokeRepeating
1 2
| Invoke(nameof(Hide), 1.5f); CancelInvoke(nameof(Hide));
|
1 2
| InvokeRepeating(nameof(Refresh), 0f, 1f); CancelInvoke(nameof(Refresh));
|
适合:
慎用:
4. async / await
1 2 3 4 5
| private async Task LoadDataAsync() { await Task.Delay(1000); Debug.Log("Loaded"); }
|
按钮调用:
1 2 3 4 5 6 7 8 9 10 11
| private async void OnClickLoad() { try { await LoadDataAsync(); } catch (Exception ex) { Debug.LogError(ex); } }
|
Unity 对象检查:
1 2 3 4 5 6
| await LoadDataAsync();
if (this == null || gameObject == null) { return; }
|
5. CancellationToken
1 2 3 4 5 6 7 8 9 10 11 12 13
| private CancellationTokenSource cts;
private void OnEnable() { cts = new CancellationTokenSource(); }
private void OnDisable() { cts?.Cancel(); cts?.Dispose(); cts = null; }
|
带取消的任务:
1 2 3 4
| private async Task LoadAsync(CancellationToken token) { await Task.Delay(1000, token); }
|
调用:
1 2 3 4 5 6 7 8
| try { await LoadAsync(cts.Token); } catch (OperationCanceledException) { Debug.Log("任务已取消"); }
|
6. Update 计时器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private float cooldown;
private void Update() { if (cooldown > 0f) { cooldown -= Time.deltaTime; } }
public bool CanUseSkill => cooldown <= 0f;
public void UseSkill() { if (!CanUseSkill) { return; }
cooldown = 3f; }
|
不受暂停影响:
1
| timer -= Time.unscaledDeltaTime;
|
7. 倒计时 UI
1 2 3 4 5 6 7 8 9 10 11 12
| private float remainTime = 10f;
private void Update() { if (remainTime <= 0f) { return; }
remainTime -= Time.deltaTime; timeText.text = Mathf.CeilToInt(remainTime).ToString(); }
|
减少 UI 刷新:
1 2 3 4 5 6 7 8 9 10 11 12
| private int lastSecond = -1;
private void Update() { int second = Mathf.CeilToInt(remainTime);
if (second != lastSecond) { timeText.text = second.ToString(); lastSecond = second; } }
|
8. 选择速查
1 2 3 4 5
| 协程 # Unity 帧流程、等待秒、分帧动画 Invoke # 简单延迟 async/await # 文件、网络、任务组合 TimerManager # 大量统一计时器 DOTween Sequence # UI 动画和串行动画流程
|
9. 常见坑速查
1 2 3 4 5 6 7
| 协程重复启动不停止旧的 # 逻辑执行多次 WaitForSeconds 受 Time.timeScale 影响 # 暂停时不走 对象销毁后 await 继续访问 UI # MissingReferenceException async void 不 try/catch # 异常难查 OnDisable 不取消任务 # 切场景后旧逻辑继续跑 Invoke 用字符串方法名 # 重构后可能失效 对象池回收不 StopCoroutine # 取出后残留旧协程
|
系列导航