线程
Unity3D是以生命周期主线程循环进行游戏开发。
Unity3D中的子线程无法运行Unity SDK(开发者工具包,软件包、软件框架)跟API(应用程序编程接口,函数库)。
限制原因:大多数游戏引擎都是主循环结构,游戏中逻辑更新和画面更新的时间点要求有确定性,必须按照帧序列严格保持同步,否则就会出现游戏中的对象不同步的现象。虽然多线程也能保证这个效果,但是引用多线程,会加大同步处理的难度与游戏的不稳定性。
但是多线程也是有好处的,如果不是画面更新,也不是常规的逻辑更新(指包括AI、物理碰撞、角色控制这些),而是一些其他后台任务,比如大量耗时的数据计算、网络请求、复杂密集的I/O操作,则可以将这个独立出来做成一个工作线程,这需要写Unity游戏的Native扩展。
协程
对于Unity3D,它是生命周期主线程循环的设计,它更倾向于使用Time slicing(时间分片)的Coroutine(协程)去完成异步任务,融合到生命周期中。
线程是操作系统级别的概念,现代操作系统都支持并实现线程,线程的调度对应用开发者是透明的,开发者无法预期某线程在何时被调度执行。基于此,一般那种随机出现的BUG,多与线程调度相关。
而协程Coroutine是编译器级别的,本质是一个线程时间片去执行代码段。它通过相关的代码使得代码段能够实现分段式的执行,显式调用yield函数后才被挂起,重新开始的地方是yield挂起的位置,每一次执行协程会跑到下一个yield语句。协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
在Unity3D中,协程是可自行停止运行 (yield),直到给定的 YieldInstruction 结束再继续运行的函数。协程 (Coroutines) 的不同用途: ·
(1) yield return null - 这一帧到此暂停,下一帧再从暂停处继续,常用于循环中。
(2) yield return new WaitForEndOfFrame - 等到这一帧的cameras和GUI渲染结束后再从此处继续,即等到这帧的末尾再往下运行。这行之后的代码还是在当前帧运行,是在下一帧开始前执行,跟return null很相似。
(3) yield return new WaitForFixedUpdate - 在下一次执行FixedUpdate的时候继续执行这段代码,即等一次物理引擎的更新。
(4) yield return new WaitForSeconds(3.0f) - 等待3秒,然后继续从此处开始,常用于做定时器。
(5) yield return WWW - 等待直至异步下载完成。
(6) yield return StartCoroutine(methodName) - 等待另一个协程执行完。这是把协程串联起来的关键,常用于让多个协程按顺序逐个运行。
(7)yield break - 直接跳出协程,对某些判定失败必须跳出的时候,比如加载AssetBundle的时候,WWW失败了,后边加载bundle没有必要了,这时候可以yield break跳出。
值得注意的是 WaitForSeconds()受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足。
以下为Unity3D的生命周期循环图
c#代码示例
Unity3D使用协程常需要用到辅助类Stopwatch,提供一组可用于准确地测量运行时间的方法和属性。
private Stopwatch frameStopwatch;//用来记录上一帧结束到现在所用的时间
private float targetFrameDuration;//自定义的每帧持续时间,防止协程过度消耗线程时间片
private void Awake()
{
frameStopwatch = new Stopwatch();
} void Update()
{
//计算每一帧所用的时间Start()之后Elapsed会一直增加,Stop()之后Elapsed的值就不变
frameStopwatch.Stop();
frameStopwatch.Reset();
frameStopwatch.Start(); if(ChunkUpdateList.Count > )
{
StartCoroutine(ProcessChunkQueueLoop());//启动协程处理ChunkUpdateList
}
} private IEnumerator ProcessChunkQueueLoop()
{
while (ChunkUpdateList.Count > 0)
{
ProcessChunkUpdateList();//每次处理ChunkUpdateList中的一个数据
if (frameStopwatch.Elapsed.TotalSeconds >= targetFrameDuration)//这一帧已经运行的时间frameStopwatch.Elapsed.TotalSeconds已经超过自定义每帧的时间targetFrameDuration,则挂起直到这一帧结束再运行
{
yield return new WaitForEndOfFrame();
}
}
}
引用:
其他:
(1)游戏主循环