通过简单的C#测试代码,更好的理解Unity中的功能。
协程,协程其实就是以IEnumerator为根本,在上面套了一层Coroutine的皮,然后在合适的时机调用迭代器的MoveNext方法。通过迭代器的Current的值,进行合适时机的选择。
在C#中,通过Console.ReadKey()模拟Unity中的每一帧。
1 static IEnumerator Coroutine() 2 { 3 Console.WriteLine("[1]开始处理数据...."); 4 Console.WriteLine("[1]结束处理数据!"); 5 yield return null; 6 Console.WriteLine("[2]开始处理数据...."); 7 Console.WriteLine("[2]结束处理数据!"); 8 yield return null; 9 Console.WriteLine("[3]开始处理数据...."); 10 Console.WriteLine("[3]结束处理数据!"); 11 yield return null; 12 Console.WriteLine("[4]结束协程..."); 13 } 14 static void Main(string[] args) 15 { 16 var e = Coroutine(); 17 bool isEnd = false; 18 while (!isEnd) 19 { 20 Console.ReadKey(); 21 Console.WriteLine("更新帧"); 22 isEnd = !e.MoveNext(); 23 } 24 }
每一次按下按键时,会执行一个yield return前的操作。
虽然一共只有3个yield return,但是一共需要按下4次按键,正如IEnumerator的延迟执行一样。每一次foreach,或者说是调用迭代器的MoveNext方法的时候才能确定是否更新Current,也就是是否有yield return语句,所以一共需要4次更新。
运行结果如下图
实际上除了yeild return null这种等待一帧的方式,我们更常用的还有返回一个 WaitForSecends用来等待一定时间,下面来实现这种效果。
为了方便接下来的测试,简单的对Console.Writeline与ReadKey进行一些包装,让我们更加方便的使用,过程比较简单就不做展示。
定义一个float类型数据,记录当前“游戏”的运行时间,一个int类型数据,记录当前“游戏”已渲染的帧数数量。
我们这里假设我们模拟的Unity,每一帧间隔的时间是稳定的,它会以0.1秒的间隔渲染每一帧游戏画面。
模拟一个Update函数,他会模拟Unity“渲染”每一帧,并记录更新游戏帧数与时间。同时输出当前游戏运行时间与已渲染的帧数数量方便我们观察。
当然,这个Update需要我们通过Readkey手动触发,毕竟只是模拟Unity。
代码如下
// 游戏运行时间 static float GameRunTime = 0f; // 游戏已渲染帧数数量 static int FrameCount = 0; static void Update() { ReadKey(); FrameCount++; GameRunTime = FrameCount * 0.1f; Print($"[{FrameCount}]游戏更新下一帧, 游戏运行时间 " + String.Format("{0:N1}", GameRunTime) + " 秒"); }
好了,这时候我们已经可以通过再Main函数通过在While(true)中调用Update函数让我们的“游戏”跑起来了。
接下来我们要实现Unity中的Coroutine与WaitForSecends。
WaitForSecends,它非常简单,只有一个float类型字段,waitfor:等待到某个时间
1 public class WaitForSecends 2 { 3 public float waitfor; 4 public WaitForSecends(float wait) 5 { 6 this.waitfor = GameRunTime + wait; 7 } 8 }
Coroutine的实现也并不复杂,我们规定协程在调用前会通过 IsCanMoveNext判断是否到达可以执行协程的条件。若不符合则继续等待,若符合则执行协程中的下一步
然后对迭代器的MoveNext方法套一层皮,如果迭代器的Current的空,则等待一帧,否则会尝试进行转Current为WaitForSecend类型(其他类型的实现也类似)。然后更新数据用以在IsCanMoveNext中计算。
直接上代码吧。
class Coroutine { public float waitFor = 0; IEnumerator core; public bool IsCanMoveNext() { return waitFor == 0 || GameRunTime >= this.waitFor; } public bool MoveNext() { bool res = core.MoveNext(); if(core.Current != null) { var w = core.Current as WaitForSecends; if(w != null) { waitFor = w.waitfor; } } else { waitFor = 0; } return res; } public Coroutine(IEnumerator e) { this.core = e; } }
然后我们声明测试函数
1 static IEnumerator CoroutineFunction() 2 { 3 Print("某个函数在协程开始时立即执行"); 4 yield return null; 5 Print("某个函数延迟一帧后执行"); 6 yield return new WaitForSecends(0.5f); 7 Print("某个函数在0.5秒后被执行"); 8 yield return new WaitForSecends(0.2f); 9 Print("某个函数在0.2秒后被执行"); 10 yield return null; 11 Print("某个函数延迟一帧后执行"); 12 }
在Main函数中调用以上这些东西
1 static void Main(string[] args) 2 { 3 Coroutine cor = new Coroutine(CoroutineFunction()); 4 while (true) 5 { 6 Update(); 7 if (cor.IsCanMoveNext()) 8 { 9 cor.MoveNext(); 10 } 11 } 12 }
可以看到“协程”已经按照我们所期望的执行时机跑起来了。其他类型的WaitFor也可以用类似的办法实现,这里就不做赘述了。
本文中功能的实现方法仅是本人的模拟手段,Unity本身实现这些方法的细节并不一定采用相同的方案,本文仅起到帮助进一步了解Unity功能本质与抛砖引玉的作用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!