赞
踩
协程(Coroutine)在我们游戏开发中有着比较重要的地位.
协程全名协同程序, 和线程不同, 协程是依附于主线程执行的, 相当于在主线程上夺取了一定的执行时间片.
也可以理解为在主线程的调用之外开辟了一个单独的调用栈, 并在协程消亡之前能保存内部信息和与其它协程共享公用信息.
协程实质是运行在主线程, 代码仍然是同步执行, 只是在某些点被挂起然后重新进行主线程其它代码的执行, 之后重新进入协程时可以从离开点继续执行. 这点和递归有些类似.
协程的核心概念有挂起和恢复, 挂起即执行到某个位置后停止, 直到被外部恢复.
同一时间只能最多有一个协程在运行, 其它协程需要被挂起.
协程有主要有以下用途:
Unity和Lua在协程概念的实现上有一些区别, 下面简单和大家分享.
Unity使用了C#的迭代器和yield关键字来实现协程.
实现原理如下:
方法返回迭代器(IEnumerator)
使用yield关键字每次往迭代器中添加一个可迭代对象(null或者继承于YieldInstruction类)
yield break或者执行完毕方法后结束协程
通过方法StartCoroutine和StopCoroutine来启动或者关闭协程
这里的可迭代对象会加入到Update, FixedUpdate等生命周期中执行
void Start()
{
var cor = StartCoroutine(Example());
StopCoroutine(cor);
}
IEnumerator Example()
{
print(Time.time);
yield return new WaitForSeconds(5);
print(Time.time);
}
在yield调用时, 可以返回一些可用的迭代对象执行预定义的行为, 当然, 也可以自定义对象.
除了返回null之外, 其它的类都是继承于YieldInstruction类, 也可以自定义类继承于CustomYieldInstruction来定义自己的协程逻辑, 对这两个类我们会在另外的文章中单独分享, 本文只是简单介绍并与Lua的协程对比.
当返回null时, Unity会在下一帧的Update和LateUpdate之间恢复协程的运行.
如下代码会在每帧打印"Test":
IEnumerator Example()
{
while(true)
{
Debug.Log("Test")
yield return null;
}
}
当然, 也可以返回数字, 如yield return 0; yield return 1
, 但是不管返回null还是数字, 都只是延迟一帧的时间, 即一个"yield return null或者数字"调用延迟一帧, 如果需要延迟多帧需要多次调用.
顾名思义, 在本帧末尾调用.
即渲染所有摄像机和GUI之后, 在屏幕上显示该帧之前调用.
有时需要将本帧准备显示的内容截图保存时, 这个调用很方便.
顾名思义, 在下一个FixedUpdate调用.
从当前帧的末尾开始, 等待一定时间再继续执行.
这个时间受Time.timeScale的影响, 可能会与实际的时间不一致.
还要注意的是, 这个调用是从当前帧的末尾开始, 如果当前帧执行时间过程, 那么实际等待的时间也会与设定的时间不一致.
与WaitForSeconds一样, 只不过使用未缩放的时间(Time.unscaledTime), 这样可以尽可能保证时间的准确性.
其它几个继承于CustomYieldInstruction的类我们在另外的文章中分享.
现在很多大型项目会使用C#与Lua结合的方式进行开发, 而Lua中也有协程的概念, 下面我们来分享一下.
Lua中的协程使用专门的包(Package)来处理, 即"coroutine".
如下所示:
local cor = coroutine.create(function() print("2222") local result = coroutine.yield() print(result) print("4444") end) print("1111") coroutine.resume(cor) print("33333") coroutine.resume(cor, "#####") print("55555") coroutine.resume(cor) -- 会打印: -- 1111 -- 2222 -- 3333 -- ##### -- 4444 -- 55555
使用coroutine.create
创建一个协程, 并使用coroutine.resume
来启动, 直到遇到coroutine.yield()
就挂起协程, 等待外部激活.
可以通过coroutine.resume(cor, "xxxx")
来给协程传递变量, 这样可以在协程内部做一些选择逻辑, 比如彻底停止协程的运行: return
.
其他接口, 如coroutine.wrap
可以在创建的时候立即启动协程, coroutine.status
可以查看协程的状态等, 有兴趣的同学可以自行学习.
下面分享协程的一些用途实例.
在游戏开发中, 异步代码的出现频率是很高的, 比如请求服务器消息, 异步加载资源等.
在请求服务器消息时, 经常需要消息的顺序调用, 但因为发消息是异步过程, 只能通过在上一个消息的回调里处理下一个消息的请求.
使用协程可以将这种代码近似写成同步的.
如下所示(Lua):
NetMgr.SendMsg("msg1", function() NetMgr.SendMsg("msg2", function() NetMgr.SendMsg("msg3", function() NetMgr.SendMsg("msg4", function() do xxxxxxxxxxxxxxxxxxxx return end end) end) end) end) ------------------------------------------------------------------- coroutine.wrap(function() NetMgr.SendMsg("msg1", function() coroutine.resume() end) coroutine.yield() NetMgr.SendMsg("msg2", function() coroutine.resume() end) coroutine.yield() NetMgr.SendMsg("msg3", function() coroutine.resume() end) coroutine.yield() NetMgr.SendMsg("msg4", function() coroutine.resume() end) coroutine.yield() do xxxxxxxxxxxxxxxxxxxx return end end)
异步切换场景, 代码如下(C#):
private void OnClick()
{
StarCoroutine(LoadScene(sceneName));
}
private IEnumerator LoadScene(sceneName)
{
var result = SceneManagement.SceneManager.LoadSceneAsync(sceneName);
while(not result.isDone)
{
yield return null;
}
Debug.Log("创建切换成功!");
}
有一些逻辑需要使用递归, 比如最常见的斐波那契数列.
如下所示(C#):
public IEnumerable Fib(int total) { int pre = 0, cur = 1; for (int count = 0; count < total; ++count) { yield return pre; int newCur = pre + cur; pre = cur; cur = newCur; } } void Main() { foreach(var itor in Fib(10)) { Console.WriteLine(itor.ToString()); } }
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。