Unity中訊息派發系統的總結(1)
訊息派發對專案中各個功能模組的解耦非常有幫助,免去模組間複雜的呼叫.
但unity自帶的訊息函式SendMessage效率一直都不高,所以基本上專案中都很少用它,而是自己寫一套訊息派發.
訊息派發原理簡單:訂閱釋出模式.
各模組需要注意哪些訊息就訂閱哪些訊息[比如我定了報紙,而我鄰居定了牛奶]
訊息派發者會儲存對應的訂閱者資訊[這個訊息派發企業業務很廣泛,既有報紙也有牛奶]
業務邏輯中需要觸發該訊息時,訊息派發者就會檢索我的訂閱資訊看看有沒有模組訂閱了該訊息,有那麼觸發這個模組的處理函式[派出快遞員,敲你大門,把報紙牛奶給你]
實現方式大致有2種:
1.
->寫訊息派發類,該類作為單例確保全域性呼叫,它包含 訂閱函式 取消訂閱函式 和 派發函式 . 以及儲存訂閱資訊的資料結構:Dictionary<string,List<IMsgHandler>>
->寫一個介面類, 如 IMsgHandler , 它宣告 訂閱者模組處理訊息的函式格式. 如: public bool OnMsgHandler(string msgName,param object[] args);
->訂閱者繼承介面類,訂閱訊息並重寫IMsgHandler介面實現自己的業務邏輯 .
2.
->寫訊息派發類,該類作為單例確保全域性呼叫,它包含 訂閱函式 取消訂閱函式 和 派發函式 . 以及儲存訂閱資訊的資料結構Dictionary<string,UnityAction<T>>
->訂閱者繼承介面類,註冊訂閱訊息函式,實現自己的業務邏輯 .
這兩種方式的區別在於第一種儲存的是物件 ,另一種儲存的是函式引用.
儲存物件:派發時 遍歷字典中訂閱的物件,然後呼叫訊息處理介面函式,所有的邏輯都在一個函式中 比較集中.
儲存函式引用:派發時 遍歷字典中訂閱的函式引用,直接呼叫該引用, 呼叫相對分散但方便
下面貼程式碼:
第一種:
介面類[IMsgReceiver.cs]
public interfaceIMsgReceiver{
// msgName:訊息名 , args:引數 , 注意返回值 如果返true 表示該訊息已經被截斷 訊息派發不會再向下派發
bool OnMsgHandler(string msgName,params object[] args);
}
訊息派發類(單例)[MsgDispatcher.cs]
public class MsgDispatcher{
private Dictionary<string ,List<IMsgRecevier>> m_Msgs = new Dictionary<string,List<IMsgReceiver>>();
private static MsgDispatcher m_Ins;
public static MsgDispatcher GetInstance(){
if(m_Ins==null){
m_Ins = new MsgDispatcher();
}
return m_Ins;
}
//訂閱訊息
public void Subscribe(string msg, IMsgReceiver recevier) {
if (recevier == null) {
Debug.LogError("SubscribeMsg : recevier == null");
return;
}
if (Check(msg, recevier)) {
Debug.LogFormat("SubscribeMsg: recevier has been subscribed ,msg={0},recevier={1}", msg, recevier.ToString());
} else {
if (this.m_Msgs.ContainsKey(msg)) {
this.m_Msgs[msg].Add(recevier);
} else {
List<IMsgReceiver> list = new List<IMsgReceiver>();
list.Add(recevier);
this.m_Msgs.Add(msg, list);
}
}
}
// 取消訂閱訊息
public void UnSubscribe(string msg, IMsgReceiver recevier) {
if (recevier == null) {
Debug.LogError("UnSubscribeMsg: recevier == null");
return;
}
if (Check(msg, recevier)) {
this.m_Msgs[msg].Remove(recevier);
}
}
public void UnSubscribe(IMsgReceiver recevier) {
if(recevier == null) {
Debug.LogError("UnSubscribeMsg: recevier == null");
return;
}
foreach(var iter in m_Msgs) {
iter.Value.Remove(recevier);
}
}
//檢查訂閱
public bool Check(string msg, IMsgReceiver recevier) {
if (m_Msgs.ContainsKey(msg)) {
var list = m_Msgs[msg];
return list.Contains(recevier);
}
return false;
}
//清除
public void ClearAll() {
m_Msgs.Clear();
}
//丟擲訊息
public void Fire(string msg, params object[] args) {
if (!this.m_Msgs.ContainsKey(msg)) {
Debug.LogWarning("Fire msg: msg has no receiver!");
return;
}
Debug.Log("[MsgDispatcher] fire msg:"+msg);
List<IMsgReceiver> list = this.m_Msgs[msg];
try {
for (int i = 0; i < list.Count; ++i) {
if (list[i] != null) {
bool bNext = list[i].OnMsgHandler(msg, args);
if (bNext) //有返回true的 訊息會被截斷,下面的handler不會接受到該訊息
{
Debug.LogWarningFormat("Fire msg: msg[{0}] has been stop fire!", msg);
break;
}
}
}
} catch {
}
}
}
最後來看看業務邏輯類如何使用.
比如 玩家類(Player.cs) 他需要訂閱牛奶訊息(Msg_Milk)
先宣告牛奶的訊息[GameEvents.cs]
public static class GameEvents{
public const string Msg_Milk = "Msg_Milk"; //牛奶訊息
}
玩家類[Player.cs]
public class Player:MonoBehaviour,IMsgReceiver{
//訂閱牛奶訊息
void OnEnable(){
MsgDispatcher.GetInstance().Subscribe(GameEvents.Msg_Milk,this);
}
//取消訂閱牛奶訊息
void OnDisable(){
MsgDispatcher.GetInstance().UnSubscribe(GameEvents.Msg_Milk,this);
}
//重寫訊息處理函式
public bool OnMsgHandler(string msgName,params object[] args){
switch(msgName){
case GameEvents.Msg_Milk:
return onMsgMilk((string)args[0]);
}
return false;
}
// 真正處理milk訊息的函式
bool onMsgMilk(string content){
Debug.Log("Player收到milk訊息,引數:"+content);
return false;
}
}
最後的最後寫一個測試類3秒後觸發牛奶訊息[Test.cs]
public class Test:MonoBehaviour{
void Awake(){{
StartCoroutine(triggerMsg());
}
IEnumerator triggerMsg(){
yield return new WaitForSeconds(3f);
//根據你的業務邏輯傳入不同引數
MsgDispatcher.GetInstance().Fire(GameEvents.Msg_Milk,"哈哈哈哈");
}
}