文章目錄
一、前言
二、學(xué)習(xí)使用協(xié)程
1.首先定義多個(gè)定時(shí)器,去實(shí)現(xiàn)游戲中的邏輯...
2.案例演示
3.開啟和終止協(xié)程
4.協(xié)程的返回值
5.案例應(yīng)用
三、總結(jié)
前言
協(xié)程在Unity開發(fā)中非常重要,但注意:協(xié)程跟多線程沒有任何關(guān)系,不要將兩者混為一談,接下來就跟大家分享一下我對(duì)協(xié)程的理解及用法!
一、協(xié)程是什么?
協(xié)程是一段在主線程中執(zhí)行的代碼邏輯,協(xié)程不是多線程。Unity的協(xié)程在每幀結(jié)束后去檢測yiled的條件是否滿足。
二、學(xué)習(xí)使用協(xié)程
1.首先定義多個(gè)定時(shí)器,去實(shí)現(xiàn)游戲中的邏輯...
代碼如下:
float timer1 = 3f;
float timer2 = 5f;
float timer3 = 8f;
void Update()
{
timer1 -= Time.deltaTime;
if (timer1 <= 0)
{
Debug.Log("3s過后...");
timer1 = 3f;
}
}
相信大家都寫過類似代碼,這種代碼如果項(xiàng)目中需要多個(gè)定時(shí)器時(shí),會(huì)顯得非常臃腫,并且我們經(jīng)常忘記做一件事情,比如忘記充值定時(shí)器...
我們都學(xué)過循環(huán),for循環(huán)中是將變量i定義為局部變量,封裝成一個(gè)代碼塊,那我們是否可以將定時(shí)器也封裝成一個(gè)代碼塊呢?如果可以的話,那么代碼應(yīng)該是這樣的:
for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
}
現(xiàn)在每一個(gè)計(jì)時(shí)器變量都成為for循環(huán)的一部分了,這看上去好多了,而且我不需要去單獨(dú)設(shè)置每一個(gè)迭代變量。 但是這段代碼放在哪里去執(zhí)行呢?start?update?顯然都不可以,所以恰好協(xié)程可以做到這一點(diǎn)。我們回顧一下協(xié)程的概念:
為了能在連續(xù)的多幀中(在這個(gè)例子中,3秒鐘等同于很多幀)調(diào)用該方法,Unity必須通過某種方式來存儲(chǔ)這個(gè)方法的狀態(tài),
-這是通過IEnumerator 中使用yield return語句得到的返回值,當(dāng)你“yield”一個(gè)方法時(shí),你相當(dāng)于說了,“現(xiàn)在暫停這個(gè)方法,
-然后在下一幀中從這里繼續(xù)執(zhí)行!”。
注意:用0或者null來yield的意思是告訴協(xié)程等待下一幀,直到繼續(xù)執(zhí)行為止。
當(dāng)然,同樣的你可以繼續(xù)yield其他協(xié)程,我會(huì)在下一個(gè)教程中講到這些。
代碼如下:
IEnumerator CountDown(){
for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
yield return 0;//現(xiàn)在停止這個(gè)方法,然后在下一幀中從這里繼續(xù)執(zhí)行!
}
Debug.Log("3s以后...");
}
2.案例演示
/*
* 接下來通過實(shí)例
* 1.實(shí)現(xiàn)打印5次--我要學(xué)游戲開發(fā)!
* 2.實(shí)現(xiàn)將這5次輸出分到每一幀里去實(shí)現(xiàn):每幀打印1次,共打印5次!
* 3.每一幀輸出“我要學(xué)游戲開發(fā)!”,無限循環(huán)。。。
通過在一個(gè)while循環(huán)中使用yield,你可以得到一個(gè)無限循環(huán)的協(xié)程,這幾乎就跟一個(gè)Update()循環(huán)等同。。。
*/
2.代碼如下:
IEnumerator SayHello5Times()
{
for (int i = 0; i < 5; i++)
{
Debug.Log("我要學(xué)游戲開發(fā)!");
yield return 0;
}
3.類似Update,代碼如下:
IEnumerator SayHello5Times()
{
while (true)
{
//1.輸出結(jié)果
Debug.Log("我要學(xué)游戲開發(fā)!");
//2.等待下一幀
yield return 0;
}
//1.輸出結(jié)果
Debug.Log("我要學(xué)游戲開發(fā)!");
//2.等待下一幀
//3. 這里永遠(yuǎn)沒有機(jī)會(huì)執(zhí)行
}
但是跟Update()不一樣的是,你可以在協(xié)程中做一些更有趣的事:
接下來做一個(gè)定時(shí)器 每隔幾秒完成某一件事
IEnumerator CountSeconds()
{
int seconds = 0;
while (true)
{
for (float timer = 0; timer < 1; timer += Time.deltaTime)
{
yield return 0;
}
seconds++;
Debug.Log("自協(xié)程啟動(dòng)以來已經(jīng)過了"+ seconds+"秒");
}
}
這個(gè)方法突出了協(xié)程一個(gè)非常酷的地方:方法的狀態(tài)被存儲(chǔ)了,這使得方法中定義的這些變量都會(huì)保存它們的值,即使是在不同的幀中。還記得這個(gè)教程開始時(shí)那些煩人的計(jì)時(shí)器變量嗎?通過協(xié)程,我們?cè)僖膊恍枰獡?dān)心它們了,只需要把變量直接放到方法里面!實(shí)際還有更優(yōu)雅的實(shí)現(xiàn)方式!稍后會(huì)跟大家講到。
3.開啟和終止協(xié)程
之前,我們已經(jīng)學(xué)過了通過 StartCoroutine()方法來開始一個(gè)協(xié)程。
如果我們想要終止所有的協(xié)程,可以通過StopAllCoroutines()方法來實(shí)現(xiàn),
注意,這只會(huì)終止在調(diào)用該方法的對(duì)象中開始的協(xié)程,對(duì)于其他的MonoBehavior類中運(yùn)行的協(xié)程不起作用。
那我們?cè)趺唇K止其中的一個(gè)協(xié)程呢?在這個(gè)例子里,這是不能的,如果你想要終止某一個(gè)特定的協(xié)程,
那么你必須得在開始協(xié)程的時(shí)候?qū)⑺姆椒鳛樽址?,就像這樣:
1、以字符串開啟/關(guān)閉,缺點(diǎn):只能有一個(gè)參數(shù)
StartCoroutine("FirstTimer");
StopCoroutine("FirstTimer”);
2、開啟帶有參數(shù)的協(xié)程的兩種方式:
StartCoroutine(Sayhi("hi"))
StartCoroutine("Sayhi","hi")
3、如何終止多個(gè)參數(shù)的協(xié)程呢?接受返回值
Coroutine stopCor_2 = StartCoroutine(Cor_2());
StopCoroutine(stopCor_2);
4、StopAllCoroutines
5、通知禁用或者銷毀方式
gameObject.SetActive(false);
//通過銷毀游戲?qū)ο蠓绞胶徒猛Ч?/span>
//Destroy(gameobject)
4.協(xié)程的返回值
協(xié)程一旦被開啟后 總是試圖將方法內(nèi)的代碼執(zhí)行完 之后停止
1.在此之前,我們yield的時(shí)候總是用0(或者null),僅僅告訴程序在繼續(xù)執(zhí)行前等待下一幀。協(xié)程最強(qiáng)大的一個(gè)功能就是它們可以通過使用yield語句來相互嵌套。
2.yield return new WaitForSeconds(n) 表示在n秒后執(zhí)行后面的代碼 但是會(huì)收到time.timescale 影響 ,如下代碼:
//隔一定時(shí)間完成某件事
IEnumerator SaySomeThings()
{
Debug.Log("協(xié)程開始執(zhí)行");
yield return StartCoroutine(Wait(1.0f));
Debug.Log("距離上一條消息已經(jīng)過去1秒了");
yield return StartCoroutine(Wait(2.5f));
Debug.Log("距離上一條消息已經(jīng)過去2.5秒了");
}
上述方法用了yield,但它并沒有用0或者null,而是用了Wait()來yield,這相當(dāng)于是說,“不再繼續(xù)執(zhí)行本程序,直到Wait程序結(jié)束”。
等待的方法還可以使用下面方式來實(shí)現(xiàn):
IEnumerator Wait(float duration)
{
for (float timer = 0; timer < duration; timer += Time.deltaTime)
yield return 0;
}
3.在協(xié)程內(nèi) 如果遇到y(tǒng)ield return StartCoroutine(test) 剩余的代碼將在子協(xié)程執(zhí)行完畢后才能繼續(xù)執(zhí)行
4.如果遇到 yield return new WaitForFixedUpdate 表示剩余代碼將在FixedUpdate 執(zhí)行完畢后執(zhí)行
5.如果遇到 yield return WWW 等待一個(gè)網(wǎng)絡(luò)請(qǐng)求完成后繼續(xù)向下執(zhí)行
6.如果遇到 yield return gameObject; 表示在gameobj不為空時(shí)向下執(zhí)行
5.案例應(yīng)用
控制對(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è)簡單腳本組件,我們可以在Inspector面板中給targetPosition和moveSpeed變量賦值,程序運(yùn)行的時(shí)候,該對(duì)象就會(huì)在協(xié)程的作用下,以我們給定的速度運(yùn)動(dòng)到給定的位置。
代碼如下:
public Vector3 targetPosition;
public float moveSpeed;
void Start1()
{
StartCoroutine(MoveToPosition(targetPosition));
}
IEnumerator MoveToPosition(Vector3 target)
{
while (transform.position != target)
{
transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
yield return 0;
}
}
這樣,這個(gè)程序并沒有通過一個(gè)計(jì)時(shí)器或者無限循環(huán),而是根據(jù)對(duì)象是否到達(dá)指定位置來yield。
我們可以讓運(yùn)動(dòng)到某一位置的程序做更多,不僅僅是一個(gè)指定位置,我們還可以通過數(shù)組來給它賦值更多的位置,通過MoveToPosition() ,我們可以讓它在這些點(diǎn)之間持續(xù)運(yùn)動(dòng)。
代碼如下:
public Vector3[] path;
void Start2()
{
StartCoroutine(MoveOnPath(true));
}
IEnumerator MoveOnPath(bool loop)
{
do
{
foreach (var point in path)
yield return StartCoroutine(MoveToPosition(point));
}
while (loop);
}
還可以加一個(gè)布爾變量,你可以控制在對(duì)象運(yùn)動(dòng)到最后一個(gè)點(diǎn)時(shí)是否要進(jìn)行循環(huán)。
課堂練習(xí):嘗試讓物體在某個(gè)點(diǎn)停留3s
如果把Wait()程序加進(jìn)來,這樣就能讓我們的對(duì)象在某個(gè)點(diǎn)就可以選擇是否暫停下來,就像一個(gè)正在巡邏的AI守衛(wèi)一樣,并且這種實(shí)現(xiàn)方式看起來非常優(yōu)雅!
三、總結(jié)
l 多個(gè)協(xié)程可以同時(shí)運(yùn)行,它們會(huì)根據(jù)各自的啟動(dòng)順序來更新;
l 協(xié)程可以嵌套任意多層(在這個(gè)例子中我們只嵌套了一層);
l 如果你想讓多個(gè)腳本訪問一個(gè)協(xié)程,那么你可以定義靜態(tài)的協(xié)程;
l 協(xié)程不是多線程(盡管它們看上去是這樣的),它們運(yùn)行在同一線程中,跟普通的腳本一樣;
l 如果你的程序需要進(jìn)行大量的計(jì)算,那么可以考慮在一個(gè)隨時(shí)間進(jìn)行的協(xié)程中處理它們;
l IEnumerator類型的方法不能帶ref或者out型的參數(shù),但可以帶被傳遞的引用;
l 協(xié)程有多種開啟和終止的方法,但是最好用哪種方式開啟,就是用哪種方式終止。
更多關(guān)于“Unity”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學(xué),課程大綱緊跟企業(yè)需求,更科學(xué)更嚴(yán)謹(jǐn),每年培養(yǎng)泛IT人才近2萬人。不論你是零基礎(chǔ)還是想提升,都可以找到適合的班型,千鋒教育隨時(shí)歡迎你來試聽。