Unity3D中的Coroutine詳解,unity3dcoroutine
Unity中的coroutine是通過yield expression;來實現的。官方指令碼中到處會看到這樣的代碼。
疑問:
yield是什嗎?
Coroutine是什嗎?
unity的coroutine程式執行流程怎麼那麼奇怪?
unity中的coroutine原理是什麼,怎麼實現的?
使用unity的coroutine需要注意什麼問題?
一、yield的在幾種語言中的程式執行特性:
Lua中的yield是使得協同函數運行->掛起並且傳遞參數給resume。resume使得協同函數掛起->運行並且傳遞參數給協同函數。
C#中yield return/break是用於函數查詢集合產生器裡面的值(類似迭代)返回,並記錄當前現場,下次查詢時從上一次記錄的yield現場處,繼續往下執行,直到繼續往下執行沒有了,那麼退出這段yield的邏輯。yield break會終止掉yield迭代邏輯並跳出。
YieldImplementation:
1).Caller callsfunction
2).Caller requestsitem 按需請求一個元素
3).Next itemreturned 返回請求的元素
4).Goto step #2
Python中的yield expression, 有yield的函數就變成了一個產生器,調用該函數返回的是迭代器對象,用迭代器對象調用next方法(或者迴圈中會自動調用next方法),才開始執行函數,執行到yield expression處則中斷,返回迭代器當前的值,並保留現場,下次調用next則從現場處開始執行,迭代完了就停止了。可以看出Python中的yield和C#中的yield是類似的,用於建立產生器,執行時中斷返回迭代器值,並記錄現場,下次從現場處繼續執行。
Unity中的yield就是和C#,python中的類似,因為unity是基於.net架構的,且unity指令碼開始是用Boo(Python的一個變種)寫的。只是unity中多了coroutine屬性類別型,和StartCoroutine的coroutine管理類。StartCoroutine不是啟動了一個新的線程,而是開啟一個協同程式,預設unity所有代碼都在一個線程中(http://answers.unity3d.com/questions/280597/new-thread-vs-startcoroutine.html)。
二、Unity的Coroutine執行現象:
第一種方法:
voidStart()
{
print("Starting " +Time.time);----------------------------------------1
StartCoroutine(WaitAndPrint(2));-------------------------------------2
print("Done " +Time.time);-------------------------------------------3
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);------------------------4
print("WaitAndPrint " + Time.time);----------------------------------5
}
該段代碼的執行順序是12435
IEnumerator Start()
{
print("Starting " +Time.time);----------------------------------------1
yield return StartCoroutine(WaitAndPrint(2.0F));------------------------2
print("Done " +Time.time);------------------------------------------3
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);----------------------------4
print("WaitAndPrint " + Time.time);-----------------------------------------5
}
該段代碼的執行順序是12453
Why?這麼奇怪的執行方式。
三、Unity官方文檔對coroutine的解釋:
Normal coroutine updates are run after theUpdate function returns. Acoroutine is a function that can suspend its execution (yield) until the givenYieldInstruction finishes. Different uses of Coroutines:
yield; The coroutine will continue after all Update functionshave been calledon the next frame.
yield WaitForSeconds(2); Continueafter a specified time delay, after all Update functions have been called for theframe.
yield WaitForFixedUpdate(); Continue afterall FixedUpdate has been called on all scripts.
yield WWWContinue aftera WWW download has completed
yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to completefirst.
C#要在yield coroutine之間加上return關鍵字。
四、Unity中的Coroutine原理猜測:
虛擬機器分段執行機制, 同類型嵌套用棧存放實現串列執行:.NET虛擬機器在每一幀迴圈中, 會依次進入每個編譯器預定義好的入口。對於Coroutine類型,編譯器需要產生一些代碼,在Coroutine類型指定的時間或事件完成後(.net的虛擬機器用函數指標進行標記管理現場和在流程中每幀檢查時間或者事件滿足後發送訊息,將cpu所有權交給yield中斷的現場,或是通過包含不同Coroutine迭代器的多個管理類管理各個coroutine, 每幀用coroutine子類通過多態檢查時間或事件到達,將cpu所有權交給coroutine子類中斷的現場),從yield中斷後的代碼處繼續往下執行, 這樣就形成了我們看到的一個function能分段執行的機制。
而對於嵌套Coroutine類型,會串列的執行而不是並行的,可能.net虛擬機器對於同coroutine類型用棧存放,棧頂的先執行,從而實現串列執行,如果外層的不使用yield return,那麼不會串列執行,而是並存執行。於是就可以解釋上面例子中的執行次序問題。
原理圖:
見:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/
五、Unity中使用Coroutine需要注意的問題:
1.使用的地方和不能使用的地方:
必須在MonoBehaviour或繼承於MonoBehaviour的類中調用 yield coroutine。yield不可以在Update或者FixedUpdate裡使用。
2.開啟協程:
StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都可以開啟一個協程,
區別:
使用字串作為參數時,開啟協程時最多隻能傳遞一個參數,並且效能消耗會更大一點; 而使用IEnumerator 作為參數則沒有這個限制。
3.刪除協程:
1).在Unity3D中,使用StopCoroutine(stringmethodName)來終止該MonoBehaviour指定方法名的一個協同程式,使用StopAllCoroutines()來終止所有該MonoBehaviour可以終止的協同程式。
包括StartCoroutine(IEnumerator routine)的。
2).還有一種方法可以終止協同程式,即將協同程式所在gameobject的active屬性設定為false,當再次設定active為ture時,協同程式並不會再開啟;
如是將協同程式所在指令碼的enabled設定為false則不會生效。
4.js和C#中使用區別:
在C#中要使用 yield return而不是yield。
C#中yield(中斷)語句必須要在IEnumerator類型裡,C#方法的傳回型別為IEnumerator,返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);
5.協程函數返回值和參數類型,組合的設計模式:
協同程式的傳回型別為Coroutine類型。在Unity3D中,Coroutine類繼承於YieldInstruction,所以,協同程式的傳回型別只能為null、等待的幀數(frame)以及等待的時間。
協同程式的參數不能指定ref、out參數。但是,我們在使用WWW類時會經常使用到協同程式,由於在協同程式中不能傳遞參數地址(引用),也不能輸出對象,
這使得每下載一個WWW對象都得重寫一個協同程式,解決這個問題的方法是建立一個基於WWW的類(用組合模式來解決),並實現一個下載方法。如下:
using UnityEngine;
using System.Collections;
public class WWWObject : MonoBehaviour
{
public WWW www;
public WWWObject(string url)
{
if(GameVar.wwwCache)
www = WWW.LoadFromCacheOrDownload(url, GameVar.version);
else
www = new WWW(url);
}
public IEnumerator Load()
{
Debug.Log("Start loading : " + www.url);
while(!www.isDone)
{
if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress = www.progress;
yield return 1;
}
if(www.error != null)
Debug.LogError("Loading error : " + www.url + "\n" +www.error);
else
Debug.Log("End loading : " + www.url);
}
public IEnumerator LoadWithTip(string resourcesName)
{
Debug.Log("Start loading : " + www.url);
LoadScene.tipStr = "Downloading resources<" + resourcesName + "> . . .";
while(!www.isDone)
{
if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress= www.progress;
yield return 1;
}
if(www.error != null)
Debug.LogError("Loading error : " + www.url + "\n" +www.error);
else
Debug.Log("End loading : " + www.url);
}
}
調用:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class LoadResources : MonoBehaviour
{
static string url ="http://61.149.211.88/Package/test.unity3d";
public static WWW www = null;
IEnumerator Start()
{
if(!GameVar.resourcesLoaded)
{
GameVar.gameState = GameState.Jumping;
WWWObject obj = new WWWObject(url);
www = obj.www;
yield return StartCoroutine(obj.LoadWithTip("Textures"));
GameVar.resourcesLoaded = true;
GameVar.gameState = GameState.Run;
}
}
}
參考文章:
http://game.ceeger.com/forum/read.php?tid=13148
http://www.zhihu.com/question/23895384
http://blog.csdn.net/tkokof1/article/details/11842673
http://blog.csdn.net/tkokof1/article/details/12834939
http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/