翻譯自:
http://www./blog/index.php/2010/10/04/c-events-and-unity3d/
zijan譯
(括號內(nèi)是譯者自己對文章和技術(shù)的理解)
(Unity3D是現(xiàn)在越來越流行的3D游戲引擎,它支持JavaScript,c#和Boo語言。如果你是個Unity3D的愛好者,但只會JavaScript。這里有一篇文章關(guān)于處理事件和消息傳遞,也許更適合你。A Useful Messaging System)
你知道C#有一個內(nèi)置的事件機(jī)制嗎?這個東東在Unity3D里也非常好用。下面舉一個例子。
為了響應(yīng)一個GameObject的事件分發(fā),你通常要建立一個腳本繼承MonoBehaviour并且實現(xiàn)你需要的方法。比如你想對鼠標(biāo)懸停作出反應(yīng),就要創(chuàng)建OnMouseOver方法。通常代碼會像這個樣子:
- void OnMouseOver () {
- renderer.material.color = Color.red;
- }
這樣工作沒問題。但如果你想通知另外一個對象響應(yīng)這個事件(OnMouseOver事件)怎么辦?
第一種方式是保持另外對象的腳本引用,然后在你的OnMouseOver方法中調(diào)用它:
- public MyScript myScript;
- void OnMouseOver () {
- myScript.NotifyMouseOver();
- }
這樣做沒問題,但是不夠好。因為你需要一直保持另外一個對象的引用,如果想通知多個對象要保持多個引用。代碼會變得很亂。
Messages 消息
另一個辦法是用SendMessage或SendMessageUpwards方法??瓷先ミ@是解決問題的最好辦法,但是這些方法存在嚴(yán)重的缺陷,以我的觀點,你應(yīng)該盡量不去使用它們。
這些方法的語法并不靈活,你需要傳遞一個方法名字的字符串,這樣做很容易出錯。另外這些方法只能用在同一個對象的附屬關(guān)系中。換句話說你只能在下面幾種情況中調(diào)用SendMessage或SendMessageUpwards方法,這些方法的腳本被關(guān)聯(lián)到同一個GameObject中,或者被關(guān)聯(lián)到這個GameObject的祖先關(guān)系對象中。
Events 事件
幸運的是有一個更好的解決辦法,這就是C#內(nèi)置的事件機(jī)制。我不在這里過多的描述機(jī)制是如何工作的,你如果有興趣可以學(xué)習(xí)相關(guān)的知識,訪問MSDN手冊。(譯者推薦另外一篇文章,C# 中的委托和事件)
現(xiàn)在讓我們看看如何在Unity3D中使用事件機(jī)制。
- using UnityEngine;
- public class EventDispatcher : MonoBehaviour {
- public delegate void EventHandler(GameObject e);
- public event EventHandler MouseOver;
- void OnMouseOver () {
- if (MouseOver != null)
- MouseOver (this.gameObject);
- }
- }
如果你不知道這段代碼到底干什么,先不要著急。重要的是一旦你把這段代碼關(guān)聯(lián)到一個GameObject,只要在整個項目的任何一個腳本中保持這個對象,你就可以像下面這樣處理事件:
- private GameObject s;
- [...]
- s.GetComponent<EventDispatcher>().MouseOver += Listener;
- [...]
- void Listener(GameObject g) {
- // g is being hovered, do something...
- }
這種方式比用消息更靈活,因為它可以被用在任何一個腳本中,而不僅僅在同一個對象附屬關(guān)系中。如果在整個應(yīng)用中保持一個單例模式的對象,你就可以監(jiān)聽任何從這個對象分發(fā)出來的事件。
另外一個重要特點,同一個監(jiān)聽方法可以響應(yīng)不同對象的事件。通過傳遞事件源對象的引用作為參數(shù),你總會知道哪個對象分發(fā)了事件,就像我的代碼展示的那樣。(對于這句話可以這樣理解,假如游戲中扔一顆導(dǎo)彈炸死了一個小兵并導(dǎo)致坦克減血,小兵死亡和坦克減血這兩個事件都觸發(fā)了同一個監(jiān)聽方法-玩家得分,通過傳遞進(jìn)來的事件源對象,就能知道小兵還是坦克觸發(fā)了玩家得分這個監(jiān)聽方法。)
References, controllers and MVC
現(xiàn)在讓我們比較一下第一和第三種方式。在最開始的例子中(第一種方式保持另外對象的腳本引用),你需要在事件分發(fā)代碼中保持監(jiān)聽者的對象引用,我說了這不是一個好主意。在用內(nèi)置事件機(jī)制,改進(jìn)的版本中(第三種方式),你需要在監(jiān)聽者代碼中保持事件分發(fā)者的引用。你也許會問,為什么后者更好?
首先,分發(fā)者不需要知道自己事件的監(jiān)聽者是誰,不需要知道有多少監(jiān)聽者。它只負(fù)責(zé)事件的發(fā)送。在最開始的例子中(第一種方式),如果要告訴分發(fā)者停止通知監(jiān)聽者,你能想象這種程序判斷有多么笨重嗎?
事件機(jī)制中,是由監(jiān)聽者自己決定監(jiān)聽什么事件,什么時候開始監(jiān)聽,什么時候停止監(jiān)聽。像這樣的對象通常用于管理程序的狀態(tài)或者執(zhí)行某些游戲邏輯。這個就叫做控制器,借用MVC設(shè)計模式的概念。這樣我們的代碼會更清晰,不易出錯。(譯者認(rèn)為觀察者設(shè)計模式更符合)
最后一點,我喜歡重載“+=”操作符去添加監(jiān)聽方法?,F(xiàn)在你也許能夠猜到,如果想結(jié)束監(jiān)聽某個事件,可以這么寫:
- s.GetComponent<EventDispatcher>().MouseOver -= Listener;
當(dāng)然你可以創(chuàng)建一個通用的EventDispatcher類,實現(xiàn)所有GameObject能夠分發(fā)的事件??梢詤⒖聪旅娴拇a。另外在實現(xiàn)OnGUI事件時要特別小心,如果想知道為什么,讀讀這篇文章。
- using UnityEngine;
- using System.Collections;
-
- /**
- * A simple event dispatcher - allows to listen to events in one GameObject from another GameObject
- *
- * Author: Bartek Drozdz (bartek [at] everyday3d [dot] com)
- *
- * Usage:
- * Add this script to the object that is supposed to dispatch events.
- * In another objects follow this pattern to register as listener at intercept events:
-
- void Start () {
- EventDispatcher ev = GameObject.Find("someObject").GetComponent<EventDispatcher>();
- ev.MouseDown += ListeningFunction; // Register the listener (and experience the beauty of overloaded operators!)
- }
-
- void ListeningFunction (GameObject e) {
- e.transform.Rotate(20, 0, 0); // 'e' is the game object that dispatched the event
- e.GetComponent<EventDispatcher>().MouseDown -= ListeningFunction; // Remove the listener
- }
-
- * This class does not implement all standards events, nor does it allow dispatching custom events,
- * but you shold have no problem adding all the other methods.
- */
- public class EventDispatcher : MonoBehaviour
- {
-
- public delegate void EventHandler (GameObject e);
- public delegate void CollisionHandler (GameObject e, Collision c);
-
- public event EventHandler MouseOver;
- void OnMouseOver ()
- {
- if (MouseOver != null)
- MouseOver (this.gameObject);
- }
-
- public event EventHandler MouseDown;
- void OnMouseDown ()
- {
- if (MouseDown != null)
- MouseDown (this.gameObject);
- }
-
- public event EventHandler MouseEnter;
- void OnMouseEnter ()
- {
- if (MouseEnter != null)
- MouseEnter (this.gameObject);
- }
-
-
- public event EventHandler MouseExit;
- void OnMouseExit ()
- {
- if (MouseExit != null)
- MouseExit (this.gameObject);
- }
-
- public event EventHandler BecameVisible;
- void OnBecameVisible ()
- {
- if (BecameVisible != null)
- BecameVisible (this.gameObject);
- }
-
- public event EventHandler BecameInvisible;
- void OnBecameInvisible ()
- {
- if (BecameInvisible != null)
- BecameInvisible (this.gameObject);
- }
-
- public event CollisionHandler CollisionEnter;
- void OnCollisionEnter (Collision c)
- {
- if (CollisionEnter != null)
- CollisionEnter (this.gameObject, c);
- }
-
- public event CollisionHandler CollisionExit;
- void OnCollisionExit (Collision c)
- {
- if (CollisionExit != null)
- CollisionExit (this.gameObject, c);
- }
-
- }
|