Unity3D開發之基於委託的訊息框架開發
我們在開發專案的時候會發現,我們經常要在不同模組不同類之間呼叫函式。比如:我們在點選場景一個物體後,就需要對UI介面做一定的變化。通常最簡單的方式是我們直接在面板繫結持有對更改介面的函式的物件。可是如果專案比較大,類比較多的情況下,我們會發現專案變得異常混亂。更改一點需求可能會牽一髮而動全身。其實這就是程式碼的耦合性太高。
一.靜態委託事件
如果專案很小,只有幾處跨模組呼叫程式碼,我們通常會在方法所在的類內宣告一個靜態委託事件,具體形式以函式形式為準。然後我們繫結委託後,就可以直接呼叫該委託。程式碼如下:
//無參無返回值 public delegate void MyDelegate(); public static MyDelegate myDelegate; private void Start() { myDelegate += Test1; } void Test1() { Debug.Log("Run"); }
當我們在外部需要呼叫此函式時,直接呼叫該類.myDelegate()就可以了。但是,當我們專案中有很多模組之間互動,這時候我們就不能一個個聲明瞭,假如一百個呼叫我們難不成要宣告一百個委託?這時候我們就需要將我們的委託事件收集起來並且根據鍵值來呼叫對應的委託。
二.基於訊息的委託機制
我們下面要做的處理其實就是省略了對委託命名的過程並且將所有委託事件集中管理起來。通過字典的鍵值來尋找對應事件。首先,我們確定,基於訊息機制的預設設定是訊息傳送是單向的,不存在返回值。所以我們就可以省略了有返回值的委託宣告。然後,對於函式,一般分為有參和無參。所以,首先我們需要宣告兩個形式的委託。
//有引數 public delegate void NotificationDelegate(EventArgs eventArgs); //無引數 public delegate void NotificationNoArgDelegate();
然後我們就在一個單例類裡宣告兩個字典,分別儲存有參和無參的委託事件。鍵值是需要我們在外部自己設定型別,一般我們都使用列舉型別,直觀明瞭。程式碼如下:
public class MessageCenter : MonoBehaviour { public static MessageCenter Instance = null; private Dictionary<EventId, NotificationDelegate> messageListeners = new Dictionary<EventId, NotificationDelegate>();//有引數 private Dictionary<EventId, NotificationNoArgDelegate> messageNoArgsListeners = new Dictionary<EventId, NotificationNoArgDelegate>();//無引數 private void Awake() { Instance = this; } /// <summary> /// 註冊 有引數函式 /// </summary> /// <param name="id">事件id</param> /// <param name="notificationDelegate">有引數事件委託</param> public void RegisterEvent(EventId id, NotificationDelegate notificationDelegate) { if (!messageListeners.ContainsKey(id)) messageListeners.Add(id,notificationDelegate); else { NotificationDelegate notification = messageListeners[id]; notification += notificationDelegate; } } /// <summary> /// 註冊委託 無引數函式 /// </summary> /// <param name="id">事件id</param> /// <param name="notificationNoArgsDelegate">無引數事件委託</param> public void RegisterEvent(EventId id, NotificationNoArgDelegate notificationNoArgsDelegate) { if (!messageNoArgsListeners.ContainsKey(id)) messageNoArgsListeners.Add(id, notificationNoArgsDelegate); else { NotificationNoArgDelegate notification = messageNoArgsListeners[id]; notification += notificationNoArgsDelegate; } } /// <summary> /// 移除監聽 /// </summary> /// <param name="id">事件id</param> /// <param name="notificationDelegate">有引數事件委託</param> public void RemoveEvent(EventId id,NotificationDelegate notificationDelegate) { if (messageListeners.ContainsKey(id)) { NotificationDelegate notification = messageListeners[id]; notification -= notificationDelegate; if (notification == null) messageListeners.Remove(id); } } /// <summary> /// 移除監聽 /// </summary> /// <param name="id">事件id</param> /// <param name="notificationDelegate">無引數委託事件</param> public void RemoveEvent(EventId id, NotificationNoArgDelegate notificationNoArgsDelegate) { if (messageListeners.ContainsKey(id)) { NotificationNoArgDelegate notification = messageNoArgsListeners[id]; notification -= notificationNoArgsDelegate; if (notification == null) messageNoArgsListeners.Remove(id); } } /// <summary> ///訊息事件分發 /// </summary> /// <param name="id">事件ID</param> /// <param name="eventArgs">事件引數</param> public void DispatchMessage(EventId id, EventArgs eventArgs) { if (!messageListeners.ContainsKey(id)) return; messageListeners[id](eventArgs); } /// <summary> ///訊息事件分發 無引數 /// </summary> /// <param name="id">事件id</param> public void DispatchMessage(EventId id) { if (!messageNoArgsListeners.ContainsKey(id)) return; messageNoArgsListeners[id](); } }
上面是實現了我們核心的訊息中心類。下面我們就可以在專案裡應用他了。比如我們的屬性UI面板裡有一個函式:
private void Start()
{
//註冊
MessageCenter.Instance.RegisterEvent(EventId.SelectGameObject, Test1);
MessageCenter.Instance.RegisterEvent(EventId.SelectGameObject, Test2);
}
void Test1()
{
Debug.Log("選擇了一個物體");
}
void Test2(EventArgs args)
{
GameObject go = (args as GameObjectArgs).go;
Debug.Log("選擇了一個物體"+go.name);
}
而對於訊息引數型別,我們選擇統一繼承自Systerm下的EventArgs,如果我們要傳一個Gameobject,我們可以自己寫一個引數類,如下:
public class GameObjectArgs:EventArgs
{
public GameObject go;
public GameObjectArgs(GameObject go)
{
this.go = go;
}
}
相比之前我寫的萬能應用框架傳參來說,雖然寫的步驟能稍微多一些,但是減除了拆裝箱操作,也是提升了很多整體的效能。我們註冊上方法後,呼叫就直接使用DispatchMessage方法。
//呼叫
void Test3()
{
MessageCenter.Instance.DispatchMessage(EventId.SelectGameObject);
MessageCenter.Instance.DispatchMessage(EventId.SelectGameObject,new GameObjectArgs(new GameObject("試驗")) as EventArgs);
}
結尾:其實這篇文章是大上個月和我朋友討論我那個萬能應用框架時反思出來的。他說我那個使用反射機制是可以將程式碼簡單歸一化,但是缺點就是反射效率低,傳參也是有拆裝箱。他建議我使用基於委託的訊息機制。回來我就不斷想這個事情。當時寫出來的時候我也是有些不願意用,因為我要不斷宣告事件的鍵值,也就是新增列舉成員,還要為不同的引數寫不同形式的類,我覺得也很麻煩。後來專案中使用StrangeIoc後,我發現這不就是StrangeIoc的簡版嗎?只不過沒有對model view controller三個模組劃分。於是我更加堅信我這個小框架的可使用性。如果有什麼不正確的地方,歡迎指點。