該博客,只為解析,解析,解析,已經整理好,已經整理好,已經整理好。代碼核心原理套用網上最流行的那一套,也是最常用游戲開發適用的消息機制。這里面加上自己的一些優化,極大的修正(哈哈),實測,沒問題。萬一要是出現問題,歡迎童鞋可以留言給我修正。
有童鞋可能會好奇,unity里面不是有自己的一套消息發送, 例如什么SendMessage,這...這個幾乎是不能用的。
為啥不能用,看看以下是網上給的解釋,自己玩玩demo還是可以用,但是實際開發,是幾乎不能用的。
I:它實現的是一種偽監聽者模式,利用的是反射機制。
II:SendMessage效率不高,因為每次調用的時候都會去遍歷檢測自身或者子節點上要調用的方法。
III:需要知道響應事件的物件,還需要一個響應函數的函數名字符串作為參數,如果我們有多個物件都要響應某個事件怎么辦呢,或者我們不知道有哪些物件要響應事件怎么辦呢。(前面兩句話比較抽象,這句話總能看的懂吧)
(如有不理解委托,事件可以參考我的這篇帖子:https://www.cnblogs.com/u3ddjw/p/9920994.html)
1.思考
消息發送機制,也可以叫做觀察者設計模式(應該是這樣的)。
通俗易懂點講,就是 一個物體發出消息,另外一個,或者幾個物體可以同時接收到這一消息並作出各自不同的行為(反饋,處理)。
那么,首先,我們想到,需要什么?
I: 我們需要的是消息(實例),發送者。 消息(實例)+發送者=我們需要的消息,就能夠處理任何消息。
II:怎么把這個消息發送出去(消息處理中心)。
III:發送者發送(分發)消息的行為
IV:接收消息。
換一種說法:發布-訂閱模式。舉例就是定報紙,你跟郵局定了報紙,郵局就會在指定時間把報紙發下來給你;中間如果你不需要報紙了,那么你就取消這個訂閱,郵局就不會發給你了。
圖解:
2.解析
1)具體消息
public class Notification { /// <summary> /// 發送者 /// </summary> public GameObject sender; /// <summary> /// 消息內容 /// </summary> public EventArgs param; /// <summary> /// 構造函數 (初始化) /// </summary> ///<param name="sender">通知發送者 ///<param name="param">通知內容 public Notification(GameObject sender, EventArgs param) { this.sender = sender; this.param = param; }
public Notification() { } /// <summary> /// 構造函數 /// </summary> ///<param name="param"> public Notification(EventArgs param) { this.sender = null; this.param = param; } } /// <summary> /// 傳遞的消息,這個是消息類中的具體消息種類 類 /// </summary> public class EventArgsTest : EventArgs { public int id; public string name; }
Notification是一個稍微抽象一點的消息類,要傳遞一個消息(類),我前面說到了,肯定是需要知道具體發送者和具體消息類的。
而具體消息類,就是后面的EventArgsTest,這個是繼承於System.EventArgs,該類是自定義類,看到后面,可能會理解為什么這樣繼承。
2)聲明一個消息的委托
public delegate void NotificationDelegate(Notification notific);
聲明一個委托傳遞上面所說的消息類的委托,這邊通俗一點來講就是:聲明一個可以傳遞Notification 參數的方法。至於委托的用法這里就不詳訴了。
3)消息處理中心
public class NotificationCenter { private static NotificationCenter instance = null; public static NotificationCenter Get() { if (instance == null) { instance = new NotificationCenter(); return instance; } return instance; } private Dictionary<uint, NotificationDelegate> eventListeners = new Dictionary<uint, NotificationDelegate>(); public void AddEventListener(uint eventKey, NotificationDelegate listener) { if (!HasEventListener(eventKey)) { NotificationDelegate del = null; //定義方法 eventListeners[eventKey] = del;// 給委托變量賦值 } eventListeners[eventKey] += listener; //注冊接收者的監聽 } public void RemoveEventListener(uint eventKey,NotificationDelegate listener) { if (!HasEventListener(eventKey)) return; eventListeners[eventKey] -= listener; if (eventListeners[eventKey] == null) { RemoveEventListener(eventKey); } } public void RemoveEventListener(uint eventKey) { eventListeners.Remove(eventKey); } /// <summary> /// 分發事件,不需要知道發送者的情況 /// </summary> /// <param name="eventKey"></param> /// <param name="notific"></param> public void PostDispatchEvent(uint eventKey, Notification notific) { if (!HasEventListener(eventKey)) return; // eventListeners[eventKey].Invoke(notific); eventListeners[eventKey](notific); } /// <summary> /// 分發事件,需要知道發送者,具體消息的情況 /// </summary> ///<param name="eventKey">事件Key ///<param name="sender">發送者 ///<param name="param">通知內容 public void PostDispatchEvent(uint eventKey, GameObject sender, EventArgs param) { if (!HasEventListener(eventKey)) return; eventListeners[eventKey](new Notification(sender, param)); } public void PostDispatchEvent(uint eventKey) { if (!HasEventListener(eventKey)) return; eventListeners[eventKey](new Notification()); } /// <summary> /// 分發事件,不需要知道任何,只需要知道發送過來消息了 /// </summary> ///<param name="eventKey">事件Key ///<param name="param">通知內容 public void PostDispatchEvent(uint eventKey, EventArgs param) { if (!HasEventListener(eventKey)) return; eventListeners[eventKey](new Notification(param)); } /// <summary> /// 是否存在指定事件的監聽器 /// </summary> public bool HasEventListener(uint eventKey) { return eventListeners.ContainsKey(eventKey); } }
該消息機制的核心,難點也就是在這里了。
首先,既然是消息處理中心,肯定是需要一個存放傳遞消息(上面那個聲明的委托)的容器,於是聲明一個
private Dictionary<uint, OnNotification> eventListeners = new Dictionary<uint, OnNotification>();
增加,移除 傳遞消息(上面那個聲明的委托),不就是以下代碼,需要注意的是
eventListeners[eventKey] -= listener;//取消接收者的監聽 eventListeners.Remove(eventKey);//移除存放在在eventListeners為eventKey的傳遞消息(上面那個委托)
if (!HasEventListener(eventKey)) { eventListeners[eventKey] = listener; //注冊接收者的監聽 } else { eventListeners[eventKey] += listener; //注冊接收者的監聽,這個用法,是委托的一種機制,不理解的自己去百度看看委托咋回事。 }
這樣,如何存儲消息做完了。
4) 發送者發送(分發)消息的行為
/// <summary> /// 消息類型,枚舉列出,調用時需要強轉為uint /// </summary> public enum ENotificationMsgType // 消息發送的枚舉值,應該轉為uint型 { ENull = 0, //Test
ELoadResProgress = 1, }
以上代碼,寫枚舉,純是為了提高代碼可讀性及可維護性,C#中多寫枚舉,少寫那種莫名其妙的 int變量,真心感謝第一家公司對我的影響,保持良好的代碼可讀性。
EventArgsTest args = new EventArgsTest(); args.id = 3; args.name = "我是Test發送的 name 消息哦"; NotificationCenter.Get().PostDispatchEvent((uint)ENotificationMsgType.ENull, args); // NotificationCenter.Get().PostDispatchEvent((uint)ENotificationMsgType.ENull); //我就是通知,不發送具體啥消息,也是可以的哦
這邊需要理解的是 PostDispatchEvent,這個方法,這邊我 寫了三重重載,因為發送消息分三種情況,如注釋那樣
{
只需要通知發送,不需要知道發送的具體消息類型,也不需要發送者。
只需要發送具體消息類型,不需要發送者。
需要發送具體消息類型,需要發送者。
}
5)接收消息
void Awake() { NotificationCenter.Get().AddEventListener((uint)ENotificationMsgType.ENull, UpdateTest); } void OnDestroy() { NotificationCenter.Get().RemoveEventListener((uint)ENotificationMsgType.ENull, UpdateTest); } void UpdateTest(Notification e) { EventArgsTest args = e.param as EventArgsTest; if (args != null) { string strName = args.name; int strId = args.id; } }
可能你會奇怪,注冊事件和移除事件為什么這樣寫。這是一種標准寫法。
寫初始(Start),結束(OnDestroy),使得每個消息擁有一個自己的生命周期。
3.另外一種版本
(2018年12月15日,補充更新,推薦使用這個版本,但是也上述比較本質都是基本一樣的)
該版本來自siki學院:給出視頻鏈接 http://www.sikiedu.com/my/course/304
該版本優勢:①極大減少代碼量。
②極大節省人力。
上述版本傳遞消息,自定義 EventArgs的子類,遇到特殊類型,聲明的類的類型很多,增大了代碼量,這是極大的弊端。
該版本也是我無意間看到的,感覺很不錯,特來補充。
最終,如若有講述不清,錯誤之處,歡迎指正。