什么是消息機制
可以理解為:一個物體發出消息,另外一個或幾個物體會接收到這條消息並作出相應的處理。這樣做的好處就是模塊之間相互獨立,降低了模塊之間的耦合度,每個腳本都可以有收發消息的能力,把模塊之間的調用轉化成了收發消息來實現。當然,一個腳本如果要對某個消息做出反應,前提是要添加對該消息的訂閱。在消息框架中,通常會使用字典或者鏈表等數據結構來保存維護所有消息及對應的消息訂閱者。
Unity自帶的SendMessage
unity自帶的消息發送機制SendMessage其實是一種偽監聽者模式,它利用的是反射機制,它的效率是很低的,每次調用的時候都會去遍歷它自身或者父節點、子節點上對應的方法,因此我們基本不回去用自帶的SendMessage。
我們的消息機制框架圖


框架主要類
MonoBase:
public class MonoBase : MonoBehaviour { public virtual void Execute(int eventCode, object message) { } }
ManagerBase:
public class ManagerBase : MonoBase { /// <summary> /// 處理自身的消息 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="message">Message.</param> public override void Execute(int eventCode, object message) { if (!dict.ContainsKey(eventCode)) { Debug.LogWarning("沒有注冊 : " + eventCode); return; } //一旦注冊過這個消息 給所有的腳本 發過去 List<MonoBase> list = dict[eventCode]; for (int i = 0; i < list.Count; i++) { list[i].Execute(eventCode, message); } } private Dictionary<int,List<MonoBase>> dict = new Dictionary<int, List<MonoBase>>(); /// <summary> /// 添加事件 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Add(int eventCode, MonoBase mono) { List<MonoBase> list = null; //之前沒有注冊過 if (!dict.ContainsKey(eventCode)) { list = new List<MonoBase>(); list.Add(mono); dict.Add(eventCode, list); return; } //之前注冊過 list = dict[eventCode]; list.Add(mono); } /// <summary> /// 添加多個事件 /// 一個腳本關心多個事件 /// </summary> /// <param name="eventCode">Event code.</param> public void Add(int[] eventCodes, MonoBase mono) { for (int i = 0; i < eventCodes.Length; i++) { Add(eventCodes[i], mono); } } /// <summary> /// 移除事件 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Remove(int eventCode, MonoBase mono) { if (!dict.ContainsKey(eventCode)) { Debug.LogWarning("沒有這個消息" + eventCode + "注冊"); return; } List<MonoBase> list = dict[eventCode]; if (list.Count == 1) dict.Remove(eventCode); else list.Remove(mono); } /// <summary> /// 移除多個 /// </summary> /// <param name="eventCode">Event code.</param> /// <param name="mono">Mono.</param> public void Remove(int[] eventCodes, MonoBase mono) { for (int i = 0; i < eventCodes.Length; i++) { Remove(eventCodes[i], mono); } } }
MsgCenter:
/// <summary> /// 消息處理中心 /// </summary> public class MsgCenter : MonoBase { public static MsgCenter Instance = null; void Awake() { Instance = this; gameObject.AddComponent<AudioManager>(); gameObject.AddComponent<UIManager>(); gameObject.AddComponent<NetManager>(); gameObject.AddComponent<CharacterManager>(); gameObject.AddComponent<SceneMgr>(); DontDestroyOnLoad(gameObject); } /// <summary> /// 參數:區域碼、消息、消息參數 /// </summary> /// <param name="areaCode"></param> /// <param name="eventCode"></param> /// <param name="message"></param> public void Dispatch(int areaCode, int eventCode, object message) { switch (areaCode) { case AreaCode.AUDIO: AudioManager.Instance.Execute(eventCode, message); break; case AreaCode.CHARACTER: CharacterManager.Instance.Execute(eventCode, message); break; case AreaCode.NET: NetManager.Instance.Execute(eventCode, message); break; case AreaCode.GAME: break; case AreaCode.UI: UIManager.Instance.Execute(eventCode, message); break; case AreaCode.SCENE: SceneMgr.Instance.Execute(eventCode, message); break; default: break; } } }
AreaCode:
public class AreaCode { /// <summary> /// UI模塊 /// </summary> public const int UI = 0; /// <summary> /// GAME模塊 /// </summary> public const int GAME = 1; /// <summary> /// CHARACTER模塊 /// </summary> public const int CHARACTER = 2; /// <summary> /// NET模塊 /// </summary> public const int NET = 3; /// <summary> /// AUDIO模塊 /// </summary> public const int AUDIO = 4; /// <summary> /// SCENE模塊 /// </summary> public const int SCENE = 5; //.... }
UI模塊實現
UIBase:
public class UIBase : MonoBase { /// <summary> /// 自身關心的消息集合 /// </summary> private List<int> list = new List<int>(); /// <summary> /// 綁定一個或多個消息 /// </summary> /// <param name="eventCodes">Event codes.</param> protected void Bind(params int[] eventCodes) { list.AddRange(eventCodes); UIManager.Instance.Add(list.ToArray(), this); } /// <summary> /// 接觸綁定的消息 /// </summary> protected void UnBind() { UIManager.Instance.Remove(list.ToArray(), this); list.Clear(); } /// <summary> /// 自動移除綁定的消息 /// </summary> public virtual void OnDestroy() { if (list != null) UnBind(); } /// <summary> /// 發消息 /// </summary> /// <param name="areaCode">Area code.</param> /// <param name="eventCode">Event code.</param> /// <param name="message">Message.</param> public void Dispatch(int areaCode, int eventCode, object message) { MsgCenter.Instance.Dispatch(areaCode, eventCode, message); } }
UIManager:
public class UIManager : ManagerBase { public static UIManager Instance = null; void Awake() { Instance = this; } }
UIEvent:
/// <summary> /// 存儲所有的UI消息 /// </summary> public class UIEvent { public const int START_PANEL = 0;//設置開始面板的顯示 public const int REGIST_PANE = 1;//設置注冊面板的顯示 }
PanelA:
public class PanelA : UIBase { private void Awake() { Bind(UIEvent.START_PANEL);//添加對消息的關心 } public override void Execute(int eventCode, object message) { switch (eventCode) //對消息的處理 { case UIEvent.START_PANEL: DoSomeThing(); break; default: break; } } }
其他模塊
類似於UI模塊,會有一個模塊Manager單例類,繼承ManagerBase, 另外有一個模塊Base類,繼承MonoBase,該模塊需要實現收發消息功能的腳本都需要繼承這個模塊Base類,在腳本的Awke函數中添加對某消息的關心,重寫父類的Excute函數,當該消息發出時,會執行該Excute函數,對消息做出相應處理
總結
這套基於消息機制的簡易框架優點是很明顯的,條理清楚,代碼組織明確,移植性高,同時,團隊開發時交流也方便,當然,也可以對上述框架進行改進,現在是整個項目的消息都由一個字典來維護,可以考慮每個模塊維護自己的消息字典(或者其他數據結構);可以根據消息的區域碼,如果是本模塊的消息,就直接派給本模塊,而不經過消息中心,如果消息屬於其他模塊,就通過消息中心來轉發;可以定義一個消息基類MsgBase,所有消息都繼承它。使用框架時,需要注意的是,首先要初始化框架層,再執行其他邏輯。每個需要收發消息的腳本,都必須在Awake函數中添加對消息的關心,在OnDestroy方法中移除對消息的關心。