Unity 3D中不得不說的yield協程與消息傳遞


  1. 協程

  在Unity 3D中,我們剛開始寫腳本的時候肯定會遇到類似下面這樣的需求:每隔3秒發射一個煙花、怪物死亡后20秒再復活之類的。剛開始的時候喜歡把這些東西都塞到Update里面去,就像下面這樣寫。

 1 float nowTime = 3.0f;  2 bool isDead = true;  3 float deadTime = 20.0f;  4     
 5 void startFireworks()  6 {  7     // 放煙花
 8 }  9 
10 void revival() 11 { 12     // 復活
13 } 14 
15 void Update () 16 { 17     if (nowTime <= 0) 18  { 19  startFireworks(); 20         nowTime = Random.Range(2.5f, 3.5f); 21  } 22     nowTime -= Time.deltaTime; 23     if (isDead) 24  { 25         if (deadTime <= 0) 26  { 27  revival(); 28             isDead = false; 29             deadTime = 30.0f; 30  } 31         deadTime -= Time.deltaTime; 32  } 33 }

  當這樣的需求多起來時,Update中凌亂不堪,如果有需求需要添加或者修改,將顯得非常麻煩。尤其是類似死亡后復活這種需求,只是在死亡后等待30秒重新復活,其他時間根本不需要去執行,這樣放在Update里面還需要每一幀去判斷,顯得很累贅。

好在Unity 3D支持yield協程,不懂沒關系,先看看下面用協程實現上面的功能。

 1 void Start()  2 {  3  StartCoroutine(Fireworks());  4 }  5     
 6 void deadHandle()  7 {  8  StartCoroutine(Revival());  9 } 10 
11 IEnumerator Fireworks() 12 { 13     while (true) 14  { 15  startFireworks(); 16         yield return new WaitForSeconds(Random.Range(2.5f, 3.5f)); 17  } 18 } 19 
20 IEnumerator Revival() 21 { 22     yield return new WaitForSeconds(30.0f); 23  revival(); 24 }

  上面代碼中,以IEnumerator作為返回值的函數就是協程,調用StartCoroutine()開始協程,在Start函數中調用StartCoroutine(Fireworks());,說明在開始時就開始執行協程Fireworks(),在deadHandle()中調用StartCoroutine(Revival());說明是在怪物死亡時開始執行協程。

  現在再來看看協程Fireworks()和Revival()中帶有yield return的語句,yield return new WaitForSeconds(30.0f);表示現在從return這個語句處中斷執行,在30秒后繼續執行后面的代碼。

  如果想在下一幀繼續執行,就應該這樣寫 yield return null,這樣語句就會在return這里中斷,等待下一幀繼續執行后面的代碼。就像在Fireworks()里面寫的,return之后,繼續進行while判斷,為true則繼續循環,遇到yield return中斷執行,等待,反復這樣運行,就像Update一樣。當然while中的判斷條件可以自己指定,在需要中斷的時候,在外面將while中的判斷條件置為false即可。

 1 bool isContinue = true;  2 void stopFireworks()  3 {  4     isContinue = false;  5 }  6 IEnumerator Fireworks()  7 {  8     while (isContinue)  9  { 10  startFireworks(); 11         yield return new WaitForSeconds(Random.Range(2.5f, 3.5f)); 12  } 13 }

  當然也可以通過有StopCoroutine來中止協程的執行,不過這個函數是有條件的,具體可以去查閱unity文檔或者網上搜索一下,有很多資料,這里只是告訴大家有這么個東西可以用。

  有了協程,寫起腳本來真是方便了很多。協程和Update一樣,也是系統在每幀會去檢測調用,因此在協程中也是可以使用Time.deltaTime的。關於協程與Update之類的執行順序,沒有測試過,網上也有一些資料,大家可以參考,不同的Unity 3D版本具體的實現可能有出入,如果某些功能確實需要知道執行順序,那么到時候可以親測一下。

  需要注意的一點是:WaitForSeconds是受到Time.timeScale影響的,如果將其置為0,那么協程就無法執行下去了。不過yield return null不會受到影響,因為每幀會執行,只是Time.deltaTime為0了。

  2. 消息傳遞

  在游戲開發中,消息傳遞必不可少。通常有三種方式:保存別的對象的引用、Unity自帶的SendMessage和C#中的事件。

  例如一個暫停,我需要通知玩家,暫停了,不要響應鍵盤鼠標操作了;通知UI,顯示一個暫停面板;通過所有怪物,不要動了,暫停了,休息一下。 

  第一種方式是剛開始寫腳本時常用的,保存所有對象的引用,這是很麻煩的事情,我需要獲取玩家、UI和所有的怪物對象,然后調用其相應的暫停函數,這在程序規模變大之后,添加、修改和刪除是一個很大的工作量。而且很多對象之間相互引用,耦合對也很高,用起來比較麻煩。

  第二種方式是Unity 3D提供的Messages消息機制,不過網上說這種方法有很大的缺陷,而且只能通知一個父子關系的對象,不同對象之間的消息無法傳遞。沒有用過這個機制,所以也不是很清楚是不是像上面說的那樣。

  第三種方式是C#中的委托和事件,這個方法對於消息傳遞來說非常好用,從設計模式的角度上來說,就是一個典型的觀察者模式。如果你用過EasyTouch搖桿,那你就應該知道在OnEnable()中使用EasyJoystick.On_JoystickMove += OnJoystickMove;注冊自己的Move函數,在這里就是OnJoystickMove。其實EasyTouch這個就用到了C#事件,使用+=添加自己的響應函數,當發生搖桿移動時,就會調用你自己指定的OnJoystickMove函數。具體可以參考下面給出的參考資料的鏈接。

  今天就寫到這里,這些都是簡介性質的,詳細資料網上都有很多,我這些只是告訴初學者Unity 3D中有這些東西,很可能是你需要的,可以少走一些彎路。

  關於協程和C#事件,是Unity 3D中強力推薦的兩個機制,它們真的非常重要,一定要善用,大家可以體會一下。

  參考資料1:【吐血推薦】簡要分析unity3d中剪不斷理還亂的yield

  參考資料2:C# 事件和Unity3D

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM