Unity 中的IEnumerator是 C# 迭代器接口,主要用于实现协程(Coroutines),这是 Unity 中处理异步操作和时间控制的核心机制。
基本概念
1.什么是协程?
协程是一种特殊的函数,可以在执行过程中暂停,并在稍后恢复执行,而不是一次性执行完毕。
2.基本语法
csharp
using System.Collections; using UnityEngine; public class CoroutineExample : MonoBehaviour { void Start() { // 启动协程 StartCoroutine(MyCoroutine()); } IEnumerator MyCoroutine() { Debug.Log("协程开始"); // 暂停1秒 yield return new WaitForSeconds(1f); Debug.Log("1秒后"); // 暂停到下一帧 yield return null; Debug.Log("下一帧"); } }常用 yield 指令
时间控制
csharp
IEnumerator TimeControls() { // 等待1秒 yield return new WaitForSeconds(1f); // 等待固定物理更新帧 yield return new WaitForFixedUpdate(); // 等待帧结束 yield return new WaitForEndOfFrame(); // 等待真实时间(不受Time.timeScale影响) yield return new WaitForSecondsRealtime(1f); }条件等待
csharp
IEnumerator WaitForConditions() { // 等待直到条件为真 yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.Space)); // 等待当条件为真时 yield return new WaitWhile(() => Input.GetKey(KeyCode.Space)); // 等待异步操作完成 AsyncOperation asyncOp = SceneManager.LoadSceneAsync("SceneName"); yield return asyncOp; }实际应用示例
1. 延迟执行
csharp
IEnumerator DelayedAction(float delay, System.Action action) { yield return new WaitForSeconds(delay); action?.Invoke(); } // 使用 StartCoroutine(DelayedAction(2f, () => { Debug.Log("2秒后执行"); }));2. 序列动画
csharp
IEnumerator SequenceAnimation() { transform.position = startPos; yield return new WaitForSeconds(0.5f); // 移动到目标位置(耗时1秒) float elapsed = 0f; while (elapsed < 1f) { transform.position = Vector3.Lerp(startPos, targetPos, elapsed); elapsed += Time.deltaTime; yield return null; // 每帧执行 } transform.position = targetPos; }3. 分帧处理大数据
csharp
IEnumerator ProcessLargeData(List<GameObject> objects) { for (int i = 0; i < objects.Count; i++) { // 处理每个对象 ProcessObject(objects[i]); // 每处理10个对象等待一帧,避免卡顿 if (i % 10 == 0) yield return null; } }4. 网络请求
csharp
IEnumerator LoadDataFromServer(string url) { UnityWebRequest request = UnityWebRequest.Get(url); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { Debug.Log("数据加载成功: " + request.downloadHandler.text); } else { Debug.LogError("加载失败: " + request.error); } }协程控制方法
csharp
public class CoroutineControl : MonoBehaviour { private Coroutine myCoroutine; void Start() { // 启动协程并保存引用 myCoroutine = StartCoroutine(RunningCoroutine()); // 带参数的协程 StartCoroutine(ParameterCoroutine("参数", 123)); } void StopMyCoroutine() { if (myCoroutine != null) { // 停止特定协程 StopCoroutine(myCoroutine); // 或停止所有协程 // StopAllCoroutines(); } } IEnumerator RunningCoroutine() { while (true) { Debug.Log("运行中..."); yield return new WaitForSeconds(1f); } } IEnumerator ParameterCoroutine(string text, int number) { Debug.Log($"参数: {text}, {number}"); yield return null; } }嵌套协程
csharp
IEnumerator MainCoroutine() { Debug.Log("主协程开始"); // 等待子协程完成 yield return StartCoroutine(SubCoroutine()); Debug.Log("子协程完成"); } IEnumerator SubCoroutine() { yield return new WaitForSeconds(2f); Debug.Log("子协程执行"); }注意事项
1. 性能考虑
csharp
// 避免:每帧创建新的 WaitForSeconds IEnumerator BadPerformance() { while (true) { yield return new WaitForSeconds(1f); // 每循环都创建新对象 } } // 推荐:缓存 WaitForSeconds IEnumerator BetterPerformance() { WaitForSeconds wait = new WaitForSeconds(1f); while (true) { yield return wait; // 复用对象 } }2. 协程生命周期
协程在 GameObject 被禁用时不会自动停止
协程在 GameObject 被销毁时会自动停止
协程在场景切换时会被销毁
3. 错误处理
csharp
IEnumerator SafeCoroutine() { try { yield return StartCoroutine(RiskyOperation()); } catch (System.Exception e) { Debug.LogError($"协程错误: {e.Message}"); } }高级用法
自定义 YieldInstruction
csharp
public class WaitForCustomCondition : CustomYieldInstruction { private System.Func<bool> predicate; public override bool keepWaiting => !predicate(); public WaitForCustomCondition(System.Func<bool> predicate) { this.predicate = predicate; } } // 使用 yield return new WaitForCustomCondition(() => player.IsReady);协程管理器
csharp
public static class CoroutineManager { public static Coroutine Start(IEnumerator coroutine) { GameObject obj = new GameObject("CoroutineRunner"); MonoBehaviour runner = obj.AddComponent<MonoBehaviourRunner>(); Coroutine routine = runner.StartCoroutine(coroutine); // 协程完成后自动销毁 runner.StartCoroutine(Cleanup(routine, obj)); return routine; } static IEnumerator Cleanup(Coroutine coroutine, GameObject obj) { yield return coroutine; GameObject.Destroy(obj); } class MonoBehaviourRunner : MonoBehaviour { } }常见问题解决
协程不执行?
检查是否调用了
StartCoroutine()确保 MonoBehaviour 脚本已启用
检查 yield 指令是否正确
协程内存泄漏?
csharp
void OnDestroy() { // 在对象销毁时停止所有协程 StopAllCoroutines(); }协程是 Unity 中非常强大的工具,合理使用可以简化很多异步操作的实现,让代码更加清晰易读。
补充与优化
一、性能优化策略
1.缓存 Yield 指令(关键优化)
csharp
public class OptimizedCoroutines : MonoBehaviour { // ❌ 坏做法:每次循环都创建新对象 IEnumerator BadWay() { while (true) { yield return new WaitForSeconds(1f); // GC 分配 } } // ✅ 好做法:缓存重用 IEnumerator GoodWay() { var waitOneSecond = new WaitForSeconds(1f); var waitEndOfFrame = new WaitForEndOfFrame(); var waitFixedUpdate = new WaitForFixedUpdate(); while (true) { yield return waitOneSecond; // 无GC分配 } } // 🚀 高级优化:预定义常用时间间隔 private static readonly Dictionary<float, WaitForSeconds> _waitCache = new Dictionary<float, WaitForSeconds>(); public static WaitForSeconds GetWait(float seconds) { if (!_waitCache.TryGetValue(seconds, out var wait)) { wait = new WaitForSeconds(seconds); _waitCache[seconds] = wait; } return wait; } IEnumerator OptimizedWay() { yield return GetWait(0.5f); yield return GetWait(1f); yield return GetWait(0.5f); // 复用缓存 } }2.避免每帧的协程开销
csharp
public class UpdateVsCoroutine : MonoBehaviour { // ❌ 协程每帧的开销 > Update IEnumerator CoroutineUpdate() { while (true) { // 每帧执行的逻辑 transform.Rotate(Vector3.up * Time.deltaTime * 90); yield return null; // 分配开销 } } // ✅ 对于简单每帧逻辑,使用 Update 更高效 void Update() { transform.Rotate(Vector3.up * Time.deltaTime * 90); } // 🎯 最佳实践:协程适合不连续的时间操作 IEnumerator ProperUse() { // 延迟执行 yield return GetWait(2f); // 一段时间内的动画 yield return MoveToPosition(Vector3.zero, 1f); // 等待条件 yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.Space)); } }3.批量处理与分帧
csharp
public class BatchProcessing : MonoBehaviour { // 优化大数据处理 IEnumerator ProcessLargeBatch<T>(List<T> items, System.Action<T> process, int batchSize = 10) { int processed = 0; for (int i = 0; i < items.Count; i++) { process(items[i]); processed++; // 每处理 batchSize 个等待一帧 if (processed >= batchSize) { processed = 0; yield return null; // 让出一帧,避免卡顿 } } } // 智能自适应批处理 IEnumerator AdaptiveBatchProcess<T>(List<T> items, System.Action<T> process) { int batchSize = 10; float targetFrameTime = 1f / 60f; // 60FPS for (int i = 0; i < items.Count; i += batchSize) { float startTime = Time.realtimeSinceStartup; // 处理一批 int end = Mathf.Min(i + batchSize, items.Count); for (int j = i; j < end; j++) { process(items[j]); } // 自适应调整批大小 float elapsed = Time.realtimeSinceStartup - startTime; if (elapsed < targetFrameTime * 0.5f) { batchSize = Mathf.Min(batchSize * 2, 100); // 加倍,最多100 } else if (elapsed > targetFrameTime) { batchSize = Mathf.Max(batchSize / 2, 1); // 减半,最少1 } yield return null; } } }二、高级协程模式
1.协程状态机模式
csharp
public class CoroutineStateMachine : MonoBehaviour { public enum State { Idle, Moving, Attacking, Dead } private State currentState; private Coroutine currentRoutine; void ChangeState(State newState) { if (currentRoutine != null) StopCoroutine(currentRoutine); currentState = newState; switch (newState) { case State.Idle: currentRoutine = StartCoroutine(IdleState()); break; case State.Moving: currentRoutine = StartCoroutine(MovingState()); break; case State.Attacking: currentRoutine = StartCoroutine(AttackingState()); break; } } IEnumerator IdleState() { Debug.Log("进入空闲状态"); var wait = new WaitForSeconds(Random.Range(1f, 3f)); while (currentState == State.Idle) { yield return wait; // 随机切换到移动状态 if (Random.value > 0.5f) { ChangeState(State.Moving); yield break; } } } IEnumerator MovingState() { /* ... */ } IEnumerator AttackingState() { /* ... */ } }2.链式协程(Promise风格)
csharp
public class CoroutineChain { public static IEnumerator Sequence(params IEnumerator[] routines) { foreach (var routine in routines) { yield return routine; } } public static IEnumerator Parallel(MonoBehaviour runner, params IEnumerator[] routines) { List<Coroutine> coroutines = new List<Coroutine>(); foreach (var routine in routines) { coroutines.Add(runner.StartCoroutine(routine)); } // 等待所有协程完成 foreach (var coroutine in coroutines) { yield return coroutine; } } // 使用示例 IEnumerator ComplexAnimation() { yield return Sequence( MoveTo(new Vector3(0, 0, 0), 1f), Parallel( Rotate(360f, 1f), ScaleTo(Vector3.one * 2, 1f) ), WaitForSeconds(0.5f), FadeOut(1f) ); } IEnumerator MoveTo(Vector3 target, float duration) { /* ... */ } IEnumerator Rotate(float angle, float duration) { /* ... */ } IEnumerator ScaleTo(Vector3 scale, float duration) { /* ... */ } IEnumerator FadeOut(float duration) { /* ... */ } }3.带取消功能的协程
csharp
public class CancellableCoroutine { public class CancellationToken { public bool IsCancelled { get; private set; } public event System.Action OnCancel; public void Cancel() { if (!IsCancelled) { IsCancelled = true; OnCancel?.Invoke(); } } } public static IEnumerator WithCancellation(IEnumerator routine, CancellationToken token) { while (routine.MoveNext()) { if (token.IsCancelled) yield break; yield return routine.Current; } } // 使用示例 IEnumerator DownloadWithTimeout(string url, float timeout) { var cancellation = new CancellationToken(); // 设置超时 StartCoroutine(TimeoutCoroutine(timeout, cancellation)); // 执行下载 yield return WithCancellation(DownloadFile(url), cancellation); } IEnumerator TimeoutCoroutine(float delay, CancellationToken token) { yield return new WaitForSeconds(delay); token.Cancel(); Debug.Log("操作超时"); } }三、协程管理器系统
1.统一协程管理
csharp
public class CoroutineManager : MonoBehaviour { private static CoroutineManager _instance; private Dictionary<string, Coroutine> _runningCoroutines = new Dictionary<string, Coroutine>(); public static CoroutineManager Instance { get { if (_instance == null) { GameObject obj = new GameObject("CoroutineManager"); _instance = obj.AddComponent<CoroutineManager>(); DontDestroyOnLoad(obj); } return _instance; } } // 启动并跟踪协程 public Coroutine StartTrackedCoroutine(IEnumerator routine, string id = null) { if (string.IsNullOrEmpty(id)) id = Guid.NewGuid().ToString(); var coroutine = StartCoroutine(TrackedRoutine(routine, id)); _runningCoroutines[id] = coroutine; return coroutine; } private IEnumerator TrackedRoutine(IEnumerator routine, string id) { yield return routine; _runningCoroutines.Remove(id); } // 按ID停止协程 public bool StopTrackedCoroutine(string id) { if (_runningCoroutines.TryGetValue(id, out var coroutine)) { StopCoroutine(coroutine); _runningCoroutines.Remove(id); return true; } return false; } // 暂停/恢复所有协程 public void PauseAllCoroutines() { foreach (var kvp in _runningCoroutines) { // 暂停逻辑 - 需要自定义实现 // 可以使用 Time.timeScale = 0,但会影响所有协程 } } // 获取运行中协程信息 public Dictionary<string, System.Type> GetRunningCoroutinesInfo() { // 返回协程类型信息,便于调试 return new Dictionary<string, System.Type>(); } }2.优先级调度系统
csharp
public class PriorityCoroutineScheduler : MonoBehaviour { public enum Priority { Low, Normal, High, Critical } private class CoroutineTask { public IEnumerator Routine; public Priority Priority; public float StartTime; public Coroutine Coroutine; } private List<CoroutineTask> _pendingTasks = new List<CoroutineTask>(); private List<CoroutineTask> _runningTasks = new List<CoroutineTask>(); private int _maxConcurrent = 3; // 最大同时运行数 public void Schedule(IEnumerator routine, Priority priority = Priority.Normal) { var task = new CoroutineTask { Routine = routine, Priority = priority, StartTime = Time.time }; _pendingTasks.Add(task); _pendingTasks.Sort((a, b) => a.Priority.CompareTo(b.Priority) * -1); // 降序 TryStartNext(); } private void TryStartNext() { while (_runningTasks.Count < _maxConcurrent && _pendingTasks.Count > 0) { var task = _pendingTasks[0]; _pendingTasks.RemoveAt(0); task.Coroutine = StartCoroutine(RunTask(task)); _runningTasks.Add(task); } } private IEnumerator RunTask(CoroutineTask task) { yield return task.Routine; _runningTasks.Remove(task); TryStartNext(); } }四、调试与监控
1.协程性能监控
csharp
public class CoroutineProfiler : MonoBehaviour { private class CoroutineInfo { public string Name; public float StartTime; public float TotalTime; public int FrameCount; public StackTrace CreationStackTrace; } private Dictionary<Coroutine, CoroutineInfo> _activeCoroutines = new Dictionary<Coroutine, CoroutineInfo>(); public Coroutine StartProfiledCoroutine(IEnumerator routine, string name = null) { if (name == null) name = routine.ToString(); var coroutine = StartCoroutine(ProfiledRoutine(routine, name)); _activeCoroutines[coroutine] = new CoroutineInfo { Name = name, StartTime = Time.realtimeSinceStartup, CreationStackTrace = new System.Diagnostics.StackTrace(true) }; return coroutine; } private IEnumerator ProfiledRoutine(IEnumerator innerRoutine, string name) { var startTime = Time.realtimeSinceStartup; int frameCount = 0; while (innerRoutine.MoveNext()) { frameCount++; yield return innerRoutine.Current; } float duration = Time.realtimeSinceStartup - startTime; Debug.Log($"协程 '{name}' 完成: {duration:F2}s, {frameCount}帧"); } void OnGUI() { if (!Application.isEditor) return; GUILayout.BeginArea(new Rect(10, 10, 400, 300)); GUILayout.Label("运行中协程:"); foreach (var kvp in _activeCoroutines) { float duration = Time.realtimeSinceStartup - kvp.Value.StartTime; GUILayout.Label($"{kvp.Value.Name}: {duration:F2}s"); } GUILayout.EndArea(); } }2.协程异常处理
csharp
public class SafeCoroutineRunner : MonoBehaviour { public static IEnumerator WithErrorHandling(IEnumerator routine, System.Action<System.Exception> onError = null) { while (true) { object current; try { if (!routine.MoveNext()) yield break; current = routine.Current; } catch (System.Exception e) { onError?.Invoke(e); Debug.LogError($"协程错误: {e.Message}\n{e.StackTrace}"); yield break; } yield return current; } } // 重试机制 public static IEnumerator WithRetry(IEnumerator routine, int maxRetries = 3) { int attempts = 0; while (attempts < maxRetries) { try { yield return routine; break; // 成功完成 } catch (System.Exception e) { attempts++; Debug.LogWarning($"第{attempts}次尝试失败: {e.Message}"); if (attempts >= maxRetries) { Debug.LogError($"达到最大重试次数"); throw; } yield return new WaitForSeconds(Mathf.Pow(2, attempts)); // 指数退避 } } } }五、Unity 2021+ 新特性
1.UniTask 替代方案(第三方库)
csharp
// UniTask 比协程更高效,支持 async/await using Cysharp.Threading.Tasks; public class UniTaskExample : MonoBehaviour { async UniTaskVoid Start() { // 并行执行多个异步任务 await UniTask.WhenAll( LoadAssetAsync("Prefabs/Character"), LoadSceneAsync("Level1"), WaitForSecondsAsync(1f) ); // 取消支持 var cancellationToken = this.GetCancellationTokenOnDestroy(); await MoveToPositionAsync(Vector3.zero, 1f, cancellationToken); } async UniTask LoadAssetAsync(string path) { // 异步加载资源 await Resources.LoadAsync<GameObject>(path); } async UniTask WaitForSecondsAsync(float seconds) { await UniTask.Delay((int)(seconds * 1000)); } }2.C# 8.0 Async Streams
csharp
// Unity 2021.2+ 支持 C# 8.0 public async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await UniTask.Delay(100); // 模拟异步操作 yield return i; } } async UniTaskVoid ConsumeAsyncStream() { await foreach (var number in GenerateSequence()) { Debug.Log(number); } }六、最佳实践总结
DOs(推荐做)
✅缓存常用 Wait 对象减少GC
✅使用协程处理异步/时间相关逻辑
✅为长时间协程添加取消支持
✅使用协程管理器统一管理
✅添加错误处理防止崩溃传播
✅分帧处理大数据避免卡顿
DON'Ts(避免做)
❌避免每帧都用协程,简单逻辑用Update
❌不要创建大量短期协程,复用已有的
❌避免在协程中直接修改已销毁对象
❌不要依赖协程精确时序,Unity不是实时系统
❌避免多层嵌套协程,难以维护和调试
进阶建议
🚀考虑使用 UniTask替代协程获得更好性能
🚀实现协程优先级调度优化资源使用
🚀添加协程监控便于调试和性能分析
🚀使用状态机模式管理复杂协程逻辑
这些优化策略和高级模式可以帮助你构建更健壮、高性能的Unity应用。根据项目规模和需求选择合适的方案。