線程
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 > 0)
{
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)游戲主循環