真實的生活裡
真實生活裡,有句話你我一定都說過或是被交代過:
“到了叫我。",
因為真的不知道對方/我們何時會到,若每幾分鐘就打電話詢問:
“到了沒?"
是不是很煩人?(但我相信有些朋友真的會做這種事,特別是女友、老婆等級的)
在這裡講一下程式上要如何去做這件 “到了叫我" 的功能。
程式的世界裡
程式界裡大多稱這功能為 Callback、Delegates 或 Event, 我人們回想一下 Unity 裡我們好像常用到類似這樣的功能,如果你想到了 OnXXX系列(如: OnTriggerEnter、OnTriggerExit … 等等的一堆),那你的概念很接近了,程式上他們不是真正的 Callback、Delegates 或 Event,而且實作方法也不太像,但他們的確有那種 “到了叫我" 的感覺。
答案是:
- Application.logMessageReceived
- 其它(我只想到一個)
狀況實例
我現在開著車,我一路上會經過三個休息站 Q、W、E ,在休息站裡我才能打手機過去說幾句話(string)。
這裡會用到:
- event
- System.Action
沒錯!只有這二個,夠簡單了吧!以下定義出事件的樣子,Action 後的 string 是宣告我會傳入一個 string 當參數。
public event Action<string> onArrivedQ; public event Action<string> onArrivedW; public event Action<string> onArrivedE;
在我到各休息站時,就看一下誰要求過我到休息站時要回報一下,若有,就打個電話過去。
if (Input.GetKeyUp(KeyCode.Q)) { if (onArrivedQ != null) { onArrivedQ("Arrived Q"); } } else if (Input.GetKeyUp(KeyCode.W)) { if (onArrivedW != null) { onArrivedW("Arrived W"); } } else if (Input.GetKeyUp(KeyCode.E)) { if (onArrivedE != null) { onArrivedE("Arrived E"); } }
接下來,我的朋友說過:"到終點 E 時再叫我!",朋友身上有支跟我定義一樣是用一個 string 當參數的 function ,他可以把這支 function += 至我身上。
//MyDriver myDriver; void Arrived(string msg) { Debug.Log("friend get event: " + msg); } void OnEnable() { myDriver.onArrivedE += Arrived; } void OnDisable() { myDriver.onArrivedE -= Arrived; }
為避免 event 重複累加,我們會在 OnEnable() 加入事件,在 OnDisable() 時移除事件。
然後,女朋友在我們出門時,總是問東問東管東西時時的關心我,但開車中不能接手機,只要到休息站回她個電話敷衍一下報個平安即可。她身上也有跟我定義一樣是用一個 string 當參數的 function,她也可以把這支 function += 至我身上。
//MyDriver myDriver; void Arrived(string msg) { Debug.Log("girl friend get event: " + msg); } void OnEnable() { myDriver.onArrivedQ += Arrived; myDriver.onArrivedW += Arrived; myDriver.onArrivedE += Arrived; } void OnDisable() { myDriver.onArrivedQ -= Arrived; myDriver.onArrivedW -= Arrived; myDriver.onArrivedE -= Arrived; }
使用 += 可以讓事件一直累加,若是用 = 會把之前所有累加的事件全取代掉只剩目前這一個。
好了,以上就完成了 “到了叫我" 的功能,當我們到達 Q、W、E 時, Log 會顯示出朋友只有在到 E 時才收到事件,而女友則是收到所有的事件。
這個功能可以應用在很多地方,比如之前講過的 設計我們的遊戲物理-簡易重力篇 ,當我們碰到地面時可以加入 event 用來告知撞到地面了,其它想知道這個事件的元件看是要放特效或做其它有意義的事(比如鏡頭震動)。
if (rhf.collider != null && rhf.collider.gameObject.tag == "ground") { if (onGround != null) { onGround(); } }
應用在血量變化也不錯,誰需要這個就血量變化的事件就可以用 += 把自己的 fucntion 加進來。
if (lastHp != nowHp) { if (onHpChange != null) { onHpChange(nowHp); } } if (nowHp <= 0) { if (onHpZero != null) { onHpZero(nowHp); } }
提醒一下,我們只用 string、int 當參數,其實事件可傳的參數是任意的(也可以不傳),如:
- class
- struct
- System.object (什麼都能丟進去,只是接收者要對傳進來的參數做轉型)
- 其它
只要對方身上的 function 參數也樣一樣,就可以用 += 累加進去。以下列幾個示範一下:
public event Action onNothingToSay; public event Action<string> onArrivedQ; public event Action<int> onHpChange; public event Action<GameObject> onDoGameOject; public event Action<BoxCollider> onDoCollider; public event Action<someClass1, someClass2> onDoSomeSomething;
我覺得,有規劃的設計我們遊戲的事件,會讓我們的程式功能區分更清楚,才不會寫出某條件下寫了一堆呼叫別人的程式碼;但也別濫用過頭,不然寫一堆 +=、-=、OnEnable、OnDisable 其實也是很累人了。
完整程式碼:
- MyDriver.cs
using UnityEngine; using System.Collections; using System; public class MyDriver : MonoBehaviour { public event Action onArrived; public event Action<string> onArrivedQ; public event Action<string> onArrivedW; public event Action<string> onArrivedE; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (Input.GetKeyUp(KeyCode.Q)) { if (onArrivedQ != null) { onArrivedQ("Arrived Q"); } } else if (Input.GetKeyUp(KeyCode.W)) { if (onArrivedW != null) { onArrivedW("Arrived W"; } } else if (Input.GetKeyUp(KeyCode.E)) { if (onArrivedE != null) { onArrivedE("Arrived E"); } } else if (Input.GetKeyUp(KeyCode.R)) { if (onArrived != null) { onArrived(); } } }
- MyFriend.cs
using UnityEngine; using System.Collections; public class MyFriend : MonoBehaviour { public MyDriver myDriver; // Use this for initialization void Arrived(string msg) { Debug.Log("friend get event: " + msg); } void ArrivedR() { Debug.Log("friend get event R"); } void OnEnable() { myDriver.onArrivedE += Arrived; myDriver.onArrived += ArrivedR; } void OnDisable() { myDriver.onArrivedE -= Arrived; myDriver.onArrived -= ArrivedR; } void Start () { } // Update is called once per frame void Update () { } }
- MyGirlFriend.cs
using UnityEngine; using System.Collections; public class MyGirlFriend : MonoBehaviour { public MyDriver myDriver; // Use this for initialization void Arrived(string msg) { Debug.Log("girl friend get event: " + msg); } void OnEnable() { myDriver.onArrivedQ += Arrived; myDriver.onArrivedW += Arrived; myDriver.onArrivedE += Arrived; } void OnDisable() { myDriver.onArrivedQ -= Arrived; myDriver.onArrivedW -= Arrived; myDriver.onArrivedE -= Arrived; } }
大家一起好好的設計我們的遊戲吧!
Chang-Pei Lee:宣告Action 時可以設定初始值為delegate{},就不用額外檢查是否為null喔~
從FB晃到前輩的地盤,拜讀了不少的好文
感謝前輩辛苦的經驗分享,頂頂頂!
讚讚
一起加油吧
讚讚