从yield return到状态机:用C#控制台程序手写一个简易Unity协程 从yield return到状态机用C#控制台程序手写一个简易Unity协程在游戏开发领域Unity引擎的协程Coroutine机制因其优雅的异步处理能力而备受开发者青睐。但你是否好奇过这个看似神奇的暂停与恢复功能背后究竟隐藏着怎样的实现原理本文将带领你脱离Unity环境仅用C#控制台程序从零构建一个简易协程系统深入探索迭代器与状态机的精妙配合。1. 协程的本质与C#迭代器协程并非Unity的专利而是一种广泛存在于编程领域的控制流机制。在C#中yield return关键字和IEnumerator接口构成了协程实现的基石。让我们先解剖一个最简单的协程示例IEnumerator SimpleCoroutine() { Console.WriteLine(步骤1执行); yield return null; Console.WriteLine(步骤2执行); yield return new WaitForSeconds(1.5f); Console.WriteLine(步骤3在1.5秒后执行); }这段代码揭示了协程的三个关键特征分步执行通过yield return将代码分割为多个可中断的执行块状态保持每次恢复执行时能记住上次离开的位置异步等待可以暂停指定时间或直到条件满足在底层C#编译器会将包含yield return的方法转换为一个状态机类。这个自动生成的类会维护当前执行位置状态保存局部变量值实现IEnumerator接口的MoveNext/Current成员2. 构建简易协程调度器要实现协程调度我们需要模拟Unity中的MonoBehaviour核心功能。下面是一个最小化的协程调度器实现框架public class CoroutineScheduler { private ListIEnumerator _runningCoroutines new ListIEnumerator(); public void StartCoroutine(IEnumerator coroutine) { _runningCoroutines.Add(coroutine); } public void Update() { for(int i 0; i _runningCoroutines.Count; ) { var coroutine _runningCoroutines[i]; if(!coroutine.MoveNext()) { _runningCoroutines.RemoveAt(i); continue; } // 处理等待对象 if(coroutine.Current is IWaitCondition wait) { if(!wait.Check()) continue; } i; } } }关键组件说明组件职责实现要点协程列表管理所有运行中的协程使用ListIEnumerator存储Update循环驱动协程执行每帧调用遍历所有协程MoveNext推进协程执行返回false表示协程结束等待机制处理暂停条件通过IWaitCondition接口实现3. 实现YieldInstruction等待机制Unity提供了丰富的等待指令如WaitForSeconds、WaitUntil等。我们可以通过接口抽象来实现这些功能interface IWaitCondition { bool Check(); } class WaitForSeconds : IWaitCondition { private float _targetTime; public WaitForSeconds(float seconds) { _targetTime Environment.TickCount seconds * 1000; } public bool Check() Environment.TickCount _targetTime; } class WaitUntil : IWaitCondition { private Funcbool _predicate; public WaitUntil(Funcbool predicate) { _predicate predicate; } public bool Check() _predicate(); }使用示例IEnumerator TestCoroutine() { Console.WriteLine(等待2秒...); yield return new WaitForSeconds(2); Console.WriteLine(等待结束); bool condition false; Console.WriteLine(等待条件满足...); yield return new WaitUntil(() condition); }4. 状态机视角下的协程执行让我们通过一个协程实例观察状态机如何工作IEnumerator ComplexCoroutine() { int counter 0; // 局部变量会被状态机保存 Console.WriteLine(阶段1); yield return new WaitForSeconds(1); counter; Console.WriteLine($阶段2, counter{counter}); yield return new WaitUntil(() counter 0); Console.WriteLine(阶段3); }编译器生成的状态机大致结构如下private sealed class ComplexCoroutined__0 : IEnumeratorobject { // 状态字段 private int 1__state; private object 2__current; // 局部变量 private int counter; object IEnumerator.Current 2__current; bool MoveNext() { switch(1__state) { case 0: 1__state -1; counter 0; Console.WriteLine(阶段1); 2__current new WaitForSeconds(1); 1__state 1; return true; case 1: 1__state -1; counter; Console.WriteLine($阶段2, counter{counter}); 2__current new WaitUntil(() counter 0); 1__state 2; return true; case 2: 1__state -1; Console.WriteLine(阶段3); return false; } return false; } }状态机的核心工作流程状态保存1__state记录当前执行位置局部变量持久化所有局部变量提升为类字段控制流转通过switch-case跳转到对应代码块返回值处理通过2__current返回等待对象5. 高级协程控制功能完整的协程系统还需要支持更丰富的控制功能。以下是几个关键扩展5.1 协程停止与嵌套public class CoroutineHandle { public IEnumerator Coroutine { get; } public bool IsDone { get; private set; } public CoroutineHandle(IEnumerator coroutine) { Coroutine coroutine; } public void Stop() { IsDone true; } } public CoroutineHandle StartCoroutine(IEnumerator coroutine) { var handle new CoroutineHandle(coroutine); _runningCoroutines.Add(new Wrapper(handle)); return handle; } private IEnumerator Wrapper(CoroutineHandle handle) { while(handle.Coroutine.MoveNext() !handle.IsDone) { yield return handle.Coroutine.Current; } handle.IsDone true; }5.2 并行协程组public IEnumerator WaitAll(params IEnumerator[] coroutines) { var handles coroutines.Select(StartCoroutine).ToList(); while(handles.Any(h !h.IsDone)) { yield return null; } } // 使用示例 yield return WaitAll( CoroutineA(), CoroutineB(), CoroutineC() );5.3 异常处理机制private IEnumerator SafeWrapper(IEnumerator coroutine) { while(true) { try { if(!coroutine.MoveNext()) break; } catch(Exception ex) { Console.WriteLine($协程异常: {ex.Message}); break; } yield return coroutine.Current; } }6. 性能优化与实践建议在实现协程系统时需要注意以下性能关键点内存分配优化重用等待对象对象池模式避免每帧创建新的委托实例预分配协程列表容量执行效率提升// 快速路径优化示例 bool MoveNext() { if(1__state 0) goto case0; if(1__state 1) goto case1; return false; case0: // 初始状态逻辑 1__state 1; return true; case1: // 后续状态逻辑 return false; }最佳实践对比表实践推荐做法不推荐做法等待对象重用静态实例每帧new新对象条件检查缓存委托实例每次创建新lambda协程嵌套控制深度3层深层嵌套调用长时间运行分帧处理单帧完成所有工作7. 实战用自制协程系统实现游戏逻辑让我们用自制的协程系统实现一个简单的敌人AIIEnumerator EnemyAI() { while(true) { // 巡逻阶段 yield return PatrolFor(5f); // 发现玩家后追击 yield return ChasePlayer(); // 攻击冷却 yield return new WaitForSeconds(2f); } } IEnumerator PatrolFor(float duration) { float startTime Environment.TickCount; while(Environment.TickCount - startTime duration * 1000) { // 实现巡逻移动逻辑 Console.WriteLine(巡逻中...); yield return null; } } IEnumerator ChasePlayer() { bool playerInSight true; while(playerInSight) { // 实现追击逻辑 Console.WriteLine(追击玩家); yield return null; // 模拟视野检查 playerInSight new Random().Next(0, 10) 2; } }这个实现展示了如何用协程优雅地组织复杂的状态行为相比传统状态机或回调方式代码更加线性直观。