Unity3D是現在越來越流行的3D遊戲引擎,它支援JavaScript,c#和Boo語言。如果你是個Unity3D的愛好者,但只會JavaScript。這裏有一篇文章關於處理事件和消息傳遞,也許更適合你。A Useful Messaging System
你知道C#有一個內置的事件機制嗎?這個東東在Unity3D裏也非常好用。下面舉一個例子。
為了回應一個GameObject的事件分發,你通常要建立一個腳本繼承MonoBehaviour並且實現你需要的方法。比如你想對滑鼠懸停作出反應,就要創建OnMouseOver方法。通常代碼會像這個樣子: < xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
C#代碼
void OnMouseOver ()
{
renderer.material.color = Color.red;
}
這樣工作沒問題。但如果你想通知另外一個物件響應這個事件(OnMouseOver事件)怎麼辦?
第一種方式是保持另外物件的腳本引用,然後在你的OnMouseOver方法中調用它:
C#代碼
public MyScript myScript;
void OnMouseOver () {
myScript.NotifyMouseOver();
}
這樣做沒問題,但是不夠好。因為你需要一直保持另外一個物件的引用,如果想通知多個物件要保持多個引用。代碼會變得很亂。
Messages 消息
另一個辦法是用SendMessage或SendMessageUpwards方法。看上去這是解決問題的最好辦法,但是這些方法存在嚴重的缺陷,以我的觀點,你應該儘量不去使用它們。
這些方法的語法並不靈活,你需要傳遞一個方法名字的字串,這樣做很容易出錯。另外這些方法只能用在同一個物件的附屬關係中。換句話說你只能在下面幾種情況中調用SendMessage或SendMessageUpwards方法,這些方法的腳本被關聯到同一個GameObject中,或者被關聯到這個GameObject的祖先關係物件中。
Events 事件
幸運的是有一個更好的解決辦法,這就是C#內置的事件機制。我不在這裏過多的描述機制是如何工作的,你如果有興趣可以學習相關的知識,訪問MSDN手冊。(C# 中的委託和事件)
現在讓我們看看如何在Unity3D中使用事件機制。
C#代碼
using UnityEngine;
public class EventDispatcher : MonoBehaviour {
public delegate void EventHandler(GameObject e);
public event EventHandler MouseOver;
void OnMouseOver () {
if (MouseOver != null)
MouseOver (this.gameObject);
}
}
如果你不知道這段代碼到底幹什麼,先不要著急。重要的是一旦你把這段代碼關聯到一個GameObject,只要在整個項目的任何一個腳本中保持這個物件,你就可以像下面這樣處理事件:
C#代碼
private GameObject s;
[...]
s.GetComponent
[...]
void Listener(GameObject g) {
// g is being hovered, do something...
}
這種方式比用消息更靈活,因為它可以被用在任何一個腳本中,而不僅僅在同一個物件附屬關係中。如果在整個應用中保持一個單例模式的物件,你就可以監聽任何從這個物件分發出來的事件。
另外一個重要特點,同一個監聽方法可以響應不同物件的事件。通過傳遞事件源對象的引用作為參數,你總會知道哪個對象分發了事件,就像我的代碼展示的那樣。(對於這句話可以這樣理解,假如遊戲中扔一顆導彈炸死了一個小兵並導致坦克減血,小兵死亡和坦克減血這兩個事件都觸發了同一個監聽方法-玩家得分,通過傳遞進來的事件源物件,就能知道小兵還是坦克觸發了玩家得分這個監聽方法。)
References, controllers and MVC
現在讓我們比較一下第一和第三種方式。在最開始的例子中(第一種方式保持另外物件的腳本引用),你需要在事件分發代碼中保持監聽者的物件引用,我說了這不是一個好主意。在用內置事件機制,改進的版本中(第三種方式),你需要在監聽者代碼中保持事件分發者的引用。你也許會問,為什麼後者更好?
首先,分發者不需要知道自己事件的監聽者是誰,不需要知道有多少監聽者。它只負責事件的發送。在最開始的例子中(第一種方式),如果要告訴分發者停止通知監聽者,你能想像這種程式判斷有多麼笨重嗎?
事件機制中,是由監聽者自己決定監聽什麼事件,什麼時候開始監聽,什麼時候停止監聽。像這樣的物件通常用於管理程式的狀態或者執行某些遊戲邏輯。這個就叫做控制器,借用MVC設計模式的概念。這樣我們的代碼會更清晰,不易出錯。(譯者認為觀察者設計模式更符合)
最後一點,我喜歡重載“+=”操作符去添加監聽方法。現在你也許能夠猜到,如果想結束監聽某個事件,可以這麼寫:
C#代碼
s.GetComponent
當然你可以創建一個通用的EventDispatcher類,實現所有GameObject能夠分發的事件。可以參看下面的代碼。另外在實現OnGUI事件時要特別小心,如果想知道為什麼,讀讀這篇文章。
C#代碼
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
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
}
* 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);
}
}