協(xié)程介紹
? ? ? ?Unity的協(xié)程系統(tǒng)是基于C#的一個(gè)簡(jiǎn)單而強(qiáng)大的接口扭屁,IEnumerator,它允許你為自己的集合類型編寫枚舉器慧起。這一點(diǎn)你不必關(guān)注太多疹吃,我們直接進(jìn)入一個(gè)簡(jiǎn)單的例子來看看協(xié)程到底能干什么。首先乓搬,我們來看一下這段簡(jiǎn)單的代碼...
倒計(jì)時(shí)器
這是一個(gè)簡(jiǎn)單的腳本組件思犁,只做了倒計(jì)時(shí),并且在到達(dá)0的時(shí)候log一個(gè)信息进肯。
還不錯(cuò)激蹲,代碼簡(jiǎn)短實(shí)用,但問題是江掩,如果我們需要復(fù)雜的腳本組件(像一個(gè)角色或者敵人的類)学辱,擁有多個(gè)計(jì)時(shí)器呢乘瓤?剛開始的時(shí)候,我們的代碼也許會(huì)是這樣的:
盡管不是太糟糕策泣,但是我個(gè)人不是很喜歡自己的代碼中充斥著這些計(jì)時(shí)器變量衙傀,它們看上去很亂,而且當(dāng)我需要重新開始計(jì)時(shí)的時(shí)候還得記得去重置它們(這活我經(jīng)常忘記做)萨咕。
如果我只用一個(gè)for循環(huán)來做這些统抬,看上去是否會(huì)好很多?
現(xiàn)在每一個(gè)計(jì)時(shí)器變量都成為for循環(huán)的一部分了危队,這看上去好多了聪建,而且我不需要去單獨(dú)設(shè)置每一個(gè)跌倒變量。
好的茫陆,你可能現(xiàn)在明白我的意思:協(xié)程可以做的正是這一點(diǎn)金麸!
碼入你的協(xié)程!
現(xiàn)在簿盅,這里提供了上面例子運(yùn)用協(xié)程的版本钱骂!我建議你從這里開始跟著我來寫一個(gè)簡(jiǎn)單的腳本組件,這樣你可以在你自己的程序中看到它是如何工作的挪鹏。
這一行用來開始我們的Countdown程序,注意愉烙,我并沒有給它傳入?yún)?shù)讨盒,但是這個(gè)方法調(diào)用了它自己(這是通過傳遞Countdown的return返回值來實(shí)現(xiàn)的)。
Yield
在Countdown方法中其他的都很好理解步责,除了兩個(gè)部分:
?IEnumerator?的返回值
?For循環(huán)中的yield?return
為了能在連續(xù)的多幀中(在這個(gè)例子中返顺,3秒鐘等同于很多幀)調(diào)用該方法,Unity必須通過某種方式來存儲(chǔ)這個(gè)方法的狀態(tài)蔓肯,這是通過IEnumerator?中使用yield?return語句得到的返回值遂鹊,當(dāng)你“yield”一個(gè)方法時(shí),你相當(dāng)于說了蔗包,“現(xiàn)在停止這個(gè)方法秉扑,然后在下一幀中從這里重新開始!”调限。
注意:用0或者null來yield的意思是告訴協(xié)程等待下一幀舟陆,直到繼續(xù)執(zhí)行為止。當(dāng)然耻矮,同樣的你可以繼續(xù)yield其他協(xié)程秦躯,我會(huì)在下一個(gè)教程中講到這些。
一些例子
協(xié)程在剛開始接觸的時(shí)候是非常難以理解的裆装,無論是新手還是經(jīng)驗(yàn)豐富的程序員我都見過他們對(duì)于協(xié)程語句一籌莫展的時(shí)候踱承。因此我認(rèn)為通過例子來理解它是最好的方法倡缠,這里有一些簡(jiǎn)單的協(xié)程例子:
多次輸出“Hello”
記住,yield?return是“停止執(zhí)行方法茎活,并且在下一幀從這里重新開始”昙沦,這意味著你可以這樣做:
每一幀輸出“Hello”,無限循環(huán)妙色。桅滋。。
通過在一個(gè)while循環(huán)中使用yield身辨,你可以得到一個(gè)無限循環(huán)的協(xié)程丐谋,這幾乎就跟一個(gè)Update()循環(huán)等同。煌珊。号俐。
計(jì)時(shí)
...不過跟Update()不一樣的是,你可以在協(xié)程中做一些更有趣的事:
這個(gè)方法突出了協(xié)程一個(gè)非扯ㄢ郑酷的地方:方法的狀態(tài)被存儲(chǔ)了吏饿,這使得方法中定義的這些變量都會(huì)保存它們的值,即使是在不同的幀中蔬浙。還記得這個(gè)教程開始時(shí)那些煩人的計(jì)時(shí)器變量嗎猪落?通過協(xié)程,我們?cè)僖膊恍枰獡?dān)心它們了畴博,只需要把變量直接放到方法里面笨忌!
開始和終止協(xié)程
之前,我們已經(jīng)學(xué)過了通過StartCoroutine()方法來開始一個(gè)協(xié)程俱病,就像這樣:
StartCoroutine(Countdown());
如果我們想要終止所有的協(xié)程官疲,可以通過StopAllCoroutines()方法來實(shí)現(xiàn),它的所要做的就跟它的名字所表達(dá)的一樣亮隙。注意途凫,這只會(huì)終止在調(diào)用該方法的對(duì)象中(應(yīng)該是指調(diào)用這個(gè)方法的類吧)開始的協(xié)程,對(duì)于其他的MonoBehavior類中運(yùn)行的協(xié)程不起作用溢吻。
如果我們有以下這樣兩條協(xié)程語句:
StartCoroutine(FirstTimer());
StartCoroutine(SecondTimer());
那我們?cè)趺唇K止其中的一個(gè)協(xié)程呢维费?在這個(gè)例子里,這是不可能的促王,如果你想要終止某一個(gè)特定的協(xié)程掩完,那么你必須得在開始協(xié)程的時(shí)候?qū)⑺姆椒鳛樽址拖襁@樣:
StartCoroutine("FirstTimer");
StartCoroutine("SecondTimer");
StopCoroutine("FirstTimer");
在第一個(gè)教程中硼砰,我們已經(jīng)了解了協(xié)程如何讓一個(gè)方法“暫颓遗睿”下來,并且讓它yield直到某些值到達(dá)我們給定的數(shù)值题翰;并且利用它恶阴,我們還創(chuàng)建了一個(gè)很棒的計(jì)時(shí)器系統(tǒng)诈胜。協(xié)程一個(gè)很重要的內(nèi)容是,它可以讓普通的程序(比方說一個(gè)計(jì)時(shí)器)很容易地被抽象化并且被復(fù)用冯事。
協(xié)程的參數(shù)
抽象化一個(gè)協(xié)程的第一個(gè)方法是給它傳遞參數(shù)焦匈,協(xié)程作為一個(gè)函數(shù)方法來說,它自然能夠傳遞參數(shù)昵仅。這里有一個(gè)協(xié)程的例子缓熟,它在特定的地方輸出了特定的信息。
嵌套的協(xié)程
在此之前摔笤,我們yield的時(shí)候總是用0(或者null)够滑,僅僅告訴程序在繼續(xù)執(zhí)行前等待下一幀。協(xié)程最強(qiáng)大的一個(gè)功能就是它們可以通過使用yield語句來相互嵌套吕世。
眼見為實(shí)彰触,我們先來創(chuàng)建一個(gè)簡(jiǎn)單的Wait()程序,不需要它做任何事命辖,只需要在運(yùn)行的時(shí)候等待一段時(shí)間就結(jié)束况毅。
IEnumerator Wait(floatduration)
{
? ? ? ? ? ? ? ? for(float ? timer?=?0;?timer?<?duration;?timer?+=?Time.deltaTime)
? ? ? ? ? ? ? ? ? ? ? ? ? ?Yield ? return 0;
}
接下來我們要編寫另一個(gè)協(xié)程,如下:
第二個(gè)方法用了yield尔艇,但它并沒有用0或者null尔许,而是用了Wait()來yield,這相當(dāng)于是說终娃,“不再繼續(xù)執(zhí)行本程序母债,直到Wait程序結(jié)束”。
現(xiàn)在尝抖,協(xié)程在程序設(shè)計(jì)方面的能力要開始展現(xiàn)了。
控制對(duì)象行為的例子
在最后一個(gè)例子中迅皇,我們就來看看協(xié)程如何像創(chuàng)建方便的計(jì)時(shí)器一樣來控制對(duì)象行為昧辽。協(xié)程不僅僅可以使用可計(jì)數(shù)的時(shí)間來yield,它還能很巧妙地利用任何條件登颓。將它與嵌套結(jié)合使用搅荞,你會(huì)得到控制游戲?qū)ο鬆顟B(tài)的最強(qiáng)大工具。
運(yùn)動(dòng)到某一位置
對(duì)于下面這個(gè)簡(jiǎn)單腳本組件框咙,我們可以在Inspector面板中給targetPosition和moveSpeed變量賦值咕痛,程序運(yùn)行的時(shí)候,該對(duì)象就會(huì)在協(xié)程的作用下喇嘱,以我們給定的速度運(yùn)動(dòng)到給定的位置茉贡。
這樣,這個(gè)程序并沒有通過一個(gè)計(jì)時(shí)器或者無限循環(huán)者铜,而是根據(jù)對(duì)象是否到達(dá)指定位置來yield腔丧。
按指定路徑前進(jìn)
我們可以讓運(yùn)動(dòng)到某一位置的程序做更多放椰,不僅僅是一個(gè)指定位置,我們還可以通過數(shù)組來給它賦值更多的位置愉粤,通過MoveToPosition() 砾医,我們可以讓它在這些點(diǎn)之間持續(xù)運(yùn)動(dòng)。
我還加了一個(gè)布爾變量衣厘,你可以控制在對(duì)象運(yùn)動(dòng)到最后一個(gè)點(diǎn)時(shí)是否要進(jìn)行循環(huán)如蚜。
把Wait()程序加進(jìn)來,這樣就能讓我們的對(duì)象在某個(gè)點(diǎn)就可以選擇是否暫停下來影暴,就像一個(gè)正在巡邏的AI守衛(wèi)一樣错邦,這真是錦上添花啊坤检!
注意:
如果你剛接觸協(xié)程兴猩,我希望這兩個(gè)教程能幫助你了解它們是如何工作的,以及如何來使用它們早歇。以下是一些在使用協(xié)程時(shí)須謹(jǐn)記的其他注意事項(xiàng):
在程序中調(diào)用StopCoroutine()方法只能終止以字符串形式啟動(dòng)(開始)的協(xié)程倾芝;
多個(gè)協(xié)程可以同時(shí)運(yùn)行,它們會(huì)根據(jù)各自的啟動(dòng)順序來更新箭跳;
協(xié)程可以嵌套任意多層(在這個(gè)例子中我們只嵌套了一層)晨另;
如果你想讓多個(gè)腳本訪問一個(gè)協(xié)程,那么你可以定義靜態(tài)的協(xié)程谱姓;
協(xié)程不是多線程(盡管它們看上去是這樣的)借尿,它們運(yùn)行在同一線程中,跟普通的腳本一樣屉来;
如果你的程序需要進(jìn)行大量的計(jì)算路翻,那么可以考慮在一個(gè)隨時(shí)間進(jìn)行的協(xié)程中處理它們;
IEnumerator類型的方法不能帶ref或者out型的參數(shù)茄靠,但可以帶被傳遞的引用茂契;
目前在Unity中沒有簡(jiǎn)便的方法來檢測(cè)作用于對(duì)象的協(xié)程數(shù)量以及具體是哪些協(xié)程作用在對(duì)象上。
【來自作者:ChevyRay】