Unity3D協程介紹 以及 使用

 

做者ChevyRay ,2013年9月28日,snaker7譯  原文地址:http://unitypatterns.com/introduction-to-coroutines/html

在Unity中,協程(Coroutines)的形式是我最喜歡的功能之一,幾乎在全部的項目中,我都會使用它來控制運動,序列,以及對象的行爲。在這個教程中,我將會說明協程是如何工做的,而且會附上一些例子來介紹它的用法。程序員

 

 

 

 

協程介紹數組

 

Unity的協程系統是基於C#的一個簡單而強大的接口 ,IEnumerator,它容許你爲本身的集合類型編寫枚舉器。這一點你沒必要關注太多,咱們直接進入一個簡單的例子來看看協程到底能幹什麼。首先,咱們來看一下這段簡單的代碼...多線程

 

倒計時器app

這是一個簡單的腳本組件,只作了倒計時,而且在到達0的時候log一個信息。函數

 

[csharp]  view plain  copy
 
  1. using Unity Engine;  
  2. using System.Collections;  
  3.    
  4. public class Countdown : MonoBehaviour  
  5. {  
  6.     public float timer = 3;  
  7.    
  8.     void Update()  
  9.     {  
  10.         timer -= Time.deltaTime;  
  11.         if(timer <= 0)  
  12.             Debug.Log("Timer has finished!");  
  13.     }  
  14. }  

 

 

還不錯,代碼簡短實用,但問題是,若是咱們須要複雜的腳本組件(像一個角色或者敵人的類),擁有多個計時器呢?剛開始的時候,咱們的代碼也許會是這樣的:工具

 

[csharp]  view plain  copy
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3.    
  4. public class MultiTimer : MonoBehaviour  
  5. {  
  6.     public float firstTimer = 3;  
  7.     public float secondTimer = 2;  
  8.     public float thirdTimer = 1;  
  9.    
  10.     void Update()  
  11.     {  
  12.         firstTimer -= Time.deltaTime;  
  13.         if(firstTimer <= 0)  
  14.             Debug.Log("First timer has finished!");  
  15.    
  16.         secondTimer -= Time.deltaTime;  
  17.         if(secondTimer <= 0)  
  18.             Debug.Log("Second timer has finished!");  
  19.    
  20.         thirdTimer -= Time.deltaTime;  
  21.         if(thirdTimer <= 0)  
  22.             Debug.Log("Third timer has finished!");  
  23.     }  
  24. }  

 

 

儘管不是太糟糕,可是我我的不是很喜歡本身的代碼中充斥着這些計時器變量,它們看上去很亂,並且當我須要從新開始計時的時候還得記得去重置它們(這活我常常忘記作)。oop

 

若是我只用一個for循環來作這些,看上去是否會好不少?post

 

[csharp]  view plain  copy
 
  1. for(float timer = 3; timer >= 0; timer -= Time.deltaTime)  
  2. {  
  3.     //Just do nothing...  
  4. }  
  5. Debug.Log("This happens after 5 seconds!");  

 

 

如今每個計時器變量都成爲for循環的一部分了,這看上去好多了,並且我不須要去單獨設置每個跌倒變量。學習

 

好的,你可能如今明白個人意思:協程能夠作的正是這一點!

 

碼入你的協程!

如今,這裏提供了上面例子運用協程的版本!我建議你從這裏開始跟着我來寫一個簡單的腳本組件,這樣你能夠在你本身的程序中看到它是如何工做的。

 

[csharp]  view plain  copy
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3.    
  4. public class CoroutineCountdown : MonoBehaviour  
  5. {  
  6.     void Start()  
  7.     {  
  8.         StartCoroutine(Countdown());  
  9.     }  
  10.    
  11.     IEnumerator Countdown()  
  12.     {  
  13.         for(floattimer = 3; timer >= 0; timer -= Time.deltaTime)  
  14.             Yield return 0;  
  15.    
  16.         Debug.Log("This message appears after 3 seconds!");  
  17.     }  
  18. }  

 

 

這看上去有點不同,不要緊,接下來我會解釋這裏到底發生了什麼。

 

 

[csharp]  view plain  copy
 
  1. StartCoroutine(Countdown());  

 

 

這一行用來開始咱們的Countdown程序,注意,我並無給它傳入參數,可是這個方法調用了它本身(這是經過傳遞Countdown的return返回值來實現的)。

 

Yield

在Countdown方法中其餘的都很好理解,除了兩個部分:

l IEnumerator 的返回值

l For循環中的yield return

 

爲了能在連續的多幀中(在這個例子中,3秒鐘等同於不少幀)調用該方法,Unity必須經過某種方式來存儲這個方法的狀態,這是經過IEnumerator 中使用yield return語句獲得的返回值,當你「yield」一個方法時,你至關於說了,「如今中止這個方法,而後在下一幀中從這裏從新開始!」。

 

注意:用0或者null來yield的意思是告訴協程等待下一幀,直到繼續執行爲止。固然,一樣的你能夠繼續yield其餘協程,我會在下一個教程中講到這些。

 

一些例子

協程在剛開始接觸的時候是很是難以理解的,不管是新手仍是經驗豐富的程序員我都見過他們對於協程語句束手無策的時候。所以我認爲經過例子來理解它是最好的方法,這裏有一些簡單的協程例子:

 

屢次輸出「Hello」

記住,yield return是「中止執行方法,而且在下一幀從這裏從新開始」,這意味着你能夠這樣作:

 

[csharp]  view plain  copy
 
  1. //This will say hello 5 times, once each frame for 5 frames  
  2. IEnumerator SayHelloFiveTimes()  
  3. {  
  4.     Yield return 0;  
  5.     Debug.Log("Hello");  
  6.     Yield return 0;  
  7.     Debug.Log("Hello");  
  8.     Yield return 0;  
  9.     Debug.Log("Hello");  
  10.     Yield return 0;  
  11.     Debug.Log("Hello");  
  12.     Yield return 0;  
  13.     Debug.Log("Hello");  
  14. }  
  15.    
  16. //This will do the exact same thing as the above function!  
  17. IEnumerator SayHello5Times()  
  18. {  
  19.     for(inti = 0; i < 5; i++)  
  20.     {  
  21.         Debug.Log("Hello");  
  22.         Yield return 0;  
  23.     }  
  24. }  

 

 

每一幀輸出「Hello」,無限循環。。。

經過在一個while循環中使用yield,你能夠獲得一個無限循環的協程,這幾乎就跟一個Update()循環等同。。。

 

[csharp]  view plain  copy
 
  1. //Once started, this will run until manually stopped or the object is destroyed  
  2. IEnumerator SayHelloEveryFrame()  
  3. {  
  4.     while(true)  
  5.     {  
  6.         //1. Say hello  
  7.         Debug.Log("Hello");  
  8.    
  9.         //2. Wait until next frame  
  10.         Yield return 0;  
  11.    
  12.     }//3. This is a forever-loop, goto 1  
  13. }  

 

 

計時

...不過跟Update()不同的是,你能夠在協程中作一些更有趣的事:

 

[csharp]  view plain  copy
 
  1. IEnumerator CountSeconds()  
  2. {  
  3.     int seconds = 0;  
  4.    
  5.     while(true)  
  6.     {  
  7.         for(float timer = 0; timer < 1; timer += Time.deltaTime)  
  8.             Yield return 0;  
  9.    
  10.         seconds++;  
  11.         Debug.Log(seconds +" seconds have passed since the Coroutine started.");  
  12.     }  
  13. }  

 

 

這個方法突出了協程一個很是酷的地方:方法的狀態被存儲了,這使得方法中定義的這些變量都會保存它們的值,即便是在不一樣的幀中。還記得這個教程開始時那些煩人的計時器變量嗎?經過協程,咱們不再須要擔憂它們了,只須要把變量直接放到方法裏面!

 

開始和終止協程

以前,咱們已經學過了經過 StartCoroutine()方法來開始一個協程,就像這樣:

 

[csharp]  view plain  copy
 
  1. StartCoroutine(Countdown());  

 

若是咱們想要終止全部的協程,能夠經過StopAllCoroutines()方法來實現,它的所要作的就跟它的名字所表達的同樣。注意,這隻會終止在調用該方法的對象中(應該是指調用這個方法的類吧)開始的協程,對於其餘的MonoBehavior類中運行的協程不起做用。

 

若是咱們有如下這樣兩條協程語句:

 

[csharp]  view plain  copy
 
  1. StartCoroutine(FirstTimer());  
  2. StartCoroutine(SecondTimer());  

 

。。。那咱們怎麼終止其中的一個協程呢?在這個例子裏,這是不可能的,若是你想要終止某一個特定的協程,那麼你必須得在開始協程的時候將它的方法名做爲字符串,就像這樣:

 

[csharp]  view plain  copy
 
  1. //If you start a Coroutine by name...  
  2. StartCoroutine("FirstTimer");  
  3. StartCoroutine("SecondTimer");  
  4.    
  5. //You can stop it anytime by name!  
  6. StopCoroutine("FirstTimer");  

 

 

更多關於協程的學習

即將爲你帶來:「Scripting with Coroutines」,一個更深刻的介紹,關於如何使用協程以及如何經過協程編寫對象行爲。

 

擴展連接

Coroutines – Unity Script Reference

若是你知道其餘很棒的關於協程的Unity教程,或者相關的主題,請在回覆中分享連接!固然,若是在教程有什麼問題,好比連接無效或者其餘一些問題,歡迎給我發郵件

 

 

 

 

 

做者ChevyRay ,2013年9月28日,snaker7譯 原文地址:http://unitypatterns.com/scripting-with-coroutines/

請注意:這個關於協程的教程共有兩部分,這是第二部分,若是您不曾看過第一部分——協程介紹,那麼在閱讀這部份內容以前建議您先了解一下。

計時器例子

第一個教程中,咱們已經瞭解了協程如何讓一個方法「暫停」下來,而且讓它yield直到某些值到達咱們給定的數值;而且利用它,咱們還建立了一個很棒的計時器系統。協程一個很重要的內容是,它可讓普通的程序(比方說一個計時器)很容易地被抽象化而且被複用。

 

協程的參數

抽象化一個協程的第一個方法是給它傳遞參數,協程做爲一個函數方法來講,它天然可以傳遞參數。這裏有一個協程的例子,它在特定的地方輸出了特定的信息。

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class TimerExample : MonoBehaviour  
  5. {  
  6.     Void Start()  
  7.     {  
  8.         //Log "Hello!" 5 times with 1 second between each log  
  9.         StartCoroutine(RepeatMessage(5, 1.0f,"Hello!"));  
  10.     }  
  11.    
  12.     IEnumerator RepeatMessage(int count,float frequency,string message)  
  13.     {  
  14.         for(int i = 0; i < count; i++)  
  15.         {  
  16.             Debug.Log(message);  
  17.             for(float timer = 0; timer < frequency; timer += Time.deltaTime)  
  18.                 Yield return 0;  
  19.                
  20.         }  
  21.     }  
  22. }  

 

嵌套的協程

在此以前,咱們yield的時候老是用0(或者null),僅僅告訴程序在繼續執行前等待下一幀。協程最強大的一個功能就是它們能夠經過使用yield語句來相互嵌套。

眼見爲實,咱們先來建立一個簡單的Wait()程序,不須要它作任何事,只須要在運行的時候等待一段時間就結束。

 

[csharp]  view plain copy
 
 
  1. IEnumerator Wait(float duration)  
  2. {  
  3.     for(float timer = 0; timer < duration; timer += Time.deltaTime)  
  4.         Yield return 0;  
  5. }  

 

接下來咱們要編寫另外一個協程,以下:

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class TimerExample : MonoBehaviour  
  5. {  
  6.     voidStart()  
  7.     {  
  8.         StartCoroutine(SaySomeThings());  
  9.     }  
  10.    
  11.     //Say some messages separated by time  
  12.     IEnumerator SaySomeThings()  
  13.     {  
  14.         Debug.Log("The routine has started");  
  15.         Yield return StartCoroutine(Wait(1.0f));  
  16.         Debug.Log("1 second has passed since the last message");  
  17.         Yield return StartCoroutine(Wait(2.5f));  
  18.         Debug.Log("2.5 seconds have passed since the last message");  
  19.     }  
  20.    
  21.     //Our wait function  
  22.     IEnumerator Wait(float duration)  
  23.     {  
  24.         for(float timer = 0; timer < duration; timer += Time.deltaTime)  
  25.             Yield return 0;  
  26.     }  
  27. }  

 

第二個方法用了yield,但它並無用0或者null,而是用了Wait()來yield,這至關因而說,「再也不繼續執行本程序,直到Wait程序結束」。

如今,協程在程序設計方面的能力要開始展示了。

 

控制對象行爲的例子

在最後一個例子中,咱們就來看看協程如何像建立方便的計時器同樣來控制對象行爲。協程不只僅可使用可計數的時間來yield,它還能很巧妙地利用任何條件。將它與嵌套結合使用,你會獲得控制遊戲對象狀態的最強大工具。

運動到某一位置

對於下面這個簡單腳本組件,咱們能夠在Inspector面板中給targetPosition和moveSpeed變量賦值,程序運行的時候,該對象就會在協程的做用下,以咱們給定的速度運動到給定的位置。

 

[csharp]  view plain copy
 
 
  1. usingUnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class MoveExample : MonoBehaviour  
  5. {  
  6.     ublic Vector3 targetPosition;  
  7.     ublic float moveSpeed;  
  8.    
  9.     Void Start()  
  10.     {  
  11.         StartCoroutine(MoveToPosition(targetPosition));  
  12.     }  
  13.    
  14.     IEnumerator MoveToPosition(Vector3 target)  
  15.     {  
  16.         while(transform.position != target)  
  17.         {  
  18.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
  19.             Yield return 0;  
  20.         }  
  21.     }  
  22. }  

 

這樣,這個程序並無經過一個計時器或者無限循環,而是根據對象是否到達指定位置來yield。

 

按指定路徑前進

咱們可讓運動到某一位置的程序作更多,不只僅是一個指定位置,咱們還能夠經過數組來給它賦值更多的位置,經過MoveToPosition() ,咱們可讓它在這些點之間持續運動。

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class MoveExample : MonoBehaviour  
  5. {  
  6.     ublic Vector3[] path;  
  7.     ublic float moveSpeed;  
  8.    
  9.     Void Start()  
  10.     {  
  11.         StartCoroutine(MoveOnPath(true));  
  12.     }  
  13.    
  14.     IEnumerator MoveOnPath(bool loop)  
  15.     {  
  16.         do  
  17.         {  
  18.             foreach(var point in path)  
  19.                 Yield return StartCoroutine(MoveToPosition(point));  
  20.         }  
  21.         while(loop);  
  22.     }  
  23.    
  24.     IEnumerator MoveToPosition(Vector3 target)  
  25.     {  
  26.         while(transform.position != target)  
  27.         {  
  28.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
  29.             Yield return 0;  
  30.         }  
  31.     }  
  32. }  

 

 

我還加了一個布爾變量,你能夠控制在對象運動到最後一個點時是否要進行循環。

把Wait()程序加進來,這樣就能讓咱們的對象在某個點就能夠選擇是否暫停下來,就像一個正在巡邏的AI守衛同樣,這真是錦上添花啊!

 

注意:

若是你剛接觸協程,我但願這兩個教程能幫助你瞭解它們是如何工做的,以及如何來使用它們。如下是一些在使用協程時須謹記的其餘注意事項:

 

  • l 在程序中調用StopCoroutine()方法只能終止以字符串形式啓動(開始)的協程;
  • l 多個協程能夠同時運行,它們會根據各自的啓動順序來更新;
  • l 協程能夠嵌套任意多層(在這個例子中咱們只嵌套了一層);
  • l 若是你想讓多個腳本訪問一個協程,那麼你能夠定義靜態的協程;
  • l 協程不是多線程(儘管它們看上去是這樣的),它們運行在同一線程中,跟普通的腳本同樣;
  • l 若是你的程序須要進行大量的計算,那麼能夠考慮在一個隨時間進行的協程中處理它們;
  • l IEnumerator類型的方法不能帶ref或者out型的參數,但能夠帶被傳遞的引用;
  • l 目前在Unity中沒有簡便的方法來檢測做用於對象的協程數量以及具體是哪些協程做用在對象上。

 

若是您發現教程中存在問題和錯誤的信息,或者有任何建議又或者您想要在這裏看到其餘須要的教程,能夠發郵件或者在評論中留言。

相關文章
相關標籤/搜索