Unity協程解析——狀態機實現的程式碼分步執行
阿新 • • 發佈:2018-12-15
Unity協程的效果
協程是一個分部執行,遇到條件(yield return 語句)會掛起,直到條件滿足才會被喚醒繼續執行後面的程式碼。
Unity在每一幀(Frame)都會去處理物件上的協程。Unity主要是在Update後去處理協程(檢查協程的條件是否滿足)
協程跟Update()其實一樣的,都是Unity每幀對會去處理的函式(如果有的話),至少是每幀的LateUpdate()後去執行。
在Unity的指令碼宣告週期中示意如下:
Unity協程的宣告和使用
示例如下:
using UnityEngine; using System.Collections; public class Example : MonoBehaviour { // 保持一個執行指令碼的引用 private IEnumerator coroutine; void Start () { print("Starting " + Time.time); //方法1:儲存協程的引用,開啟協程; { coroutine = WaitAndPrint(3.0f); StartCoroutine(coroutine); } //方法2:把協程的方法作為字串,開啟協程 { StartCoroutine("WaitAndPrint",3.0f); } print("Done " + Time.time); } // 每隔3秒,列印一次 // 由yield來掛起(暫停)函式 public IEnumerator WaitAndPrint(float waitTime) { while (true) { yield return new WaitForSeconds(waitTime); print("WaitAndPrint " + Time.time); } } }
可見,協程是通過IEnumerator實現的,每當yield return後就會掛起直到滿足條件才繼續執行後面的程式碼。
IEnumerator內部的實現
例如對於語句:
using System; using System.Collections; class Test { static IEnumerator GetCounter() { for (int count = 0; count < 10; count++) { yield return count; } } }
C#編譯器會對應生成:
internal class Test { // Note how this doesn't execute any of our original code private static IEnumerator GetCounter() { return new <GetCounter>d__0(0); } // Nested type automatically created by the compiler to implement the iterator [CompilerGenerated] private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable { // Fields: there'll always be a "state" and "current", but the "count" // comes from the local variable in our iterator block. private int <>1__state; private object <>2__current; public int <count>5__1; [DebuggerHidden] public <GetCounter>d__0(int <>1__state) { this.<>1__state = <>1__state; } // Almost all of the real work happens here private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<count>5__1 = 0; while (this.<count>5__1 < 10)//這裡針對迴圈處理 { this.<>2__current = this.<count>5__1; this.<>1__state = 1; return true; Label_004B: this.<>1__state = -1; this.<count>5__1++; } break; case 1: goto Label_004B; } return false; } ...//省略了一些方法 } }
狀態機實現程式碼分步執行
如何實現“當下次再呼叫MoveNext()方法時,我們的方法會繼續從上一個yield return語句處開始執行。”?
根據上部分C#編譯器生成的程式碼我們可以總結到:通過狀態機。
對於沒有for、while等迴圈的語句,是通過構造多個switch case語句,通過狀態切換來達到分部執行程式碼。
private bool MoveNext()
{
switch(this.<>1_state)
{
case 0:
this.<>1_state = -1;
Console.WriteLine("第一個yield return 前的程式碼);
this.<>2_current = 1;
this.<>1_state = 1;
case 1:
this.<>1_state = -1;
Console.WriteLine("第二個yield return 前的程式碼);
this.<>2_current = 2;
this.<>1_state = 2;
case 2:
this.<>1_state = -1;
Console.WriteLine("第二個yield return 後的程式碼);
break;
}
return false;
}
而對於迴圈語句,使用goto語句來實現狀態切換:
private bool MoveNext()
{
switch(this.<>1_state)
{
case 0:
this.<>1_state = -1;
this.<i>5_1 = 0;
while(this.<i>5_1 < 10)
{
this.<>2_current = this.<i>5_1;
this.<>1_state = 1;
return true;
Label_0046:
this.<>1_state = -1;
this.<i>5_1++;
}
break;
case 1:
goto Label_0046;
}
return false;
}
參考前文中的圖Unity指令碼生命週期,Unity在每幀都會呼叫某個指令碼上的協程,即呼叫協程的MoveNext函式,當MoveNext函式返回false則結束協程,否則如果返回 true ,就從當前位置繼續往下執行。
參考
- 《Unity 3D指令碼程式設計 使用C#語言開發跨平臺遊戲》第八章
- Unity3D協程進階-原理剖析: https://blog.csdn.net/yupu56/article/details/52791952