SendMessage delegate

咱們知道Unity3D自身有SendMessage向對象之間發送消息,但這個消耗是比較大的,由於它很大程度上涉及了Reflection發射機制。 php

如何變動思路,結合C#自帶的消息系統delegate委託事件,對此進行優化: 數組

咱們看如下一個簡單的delegate使用: post

[csharp]  view plain copy print ?
  1. public class DelegateBasic : MonoBehaviour {  
  2.       
  3.     //define my delegate statement.  
  4.     public delegate void MyDelegate(string arg1);  
  5.       
  6.     //create my delegate object  
  7.     public MyDelegate myDelegate;  
  8.       
  9.     // Use this for initialization  
  10.     void Start () {  
  11.         myDelegate += myFunciton1;  
  12.         myDelegate += myFunciton2;  
  13.     }  
  14.       
  15.     // Update is called once per frame  
  16.     void Update () {  
  17.       
  18.     }  
  19.       
  20.       
  21.     void OnGUI()  
  22.     {  
  23.         if(GUILayout.Button("INVOKE"))  
  24.         {  
  25.             myDelegate("Invoke....");  
  26.         }  
  27.           
  28.     }  
  29.       
  30.     void myFunciton1(string s)  
  31.     {  
  32.         Debug.Log("myFunciton1 " + s);  
  33.     }  
  34.       
  35.     void myFunciton2(string s)  
  36.     {  
  37.         Debug.Log("myFunciton2 " + s);  
  38.     }  
  39.       
  40.       
  41. }  

以上只是實現myDelegate一旦調用,那麼鏈接了該委託的事件的方法都會被調用。 學習

可是以上咱們看到,這只是1個delegate類型對應 —— 多個與該委託定義形式一致的方法;假如我須要不一樣的delegate呢?並且這些方法形式定義不同,也或者說咱們須要方法傳參不同呢? 優化


1、Notification通知中心結構:參考http://wiki.unity3d.com/index.php/Category:Messaging 這裏有不少個寫好的例子可使用。 this

[csharp]  view plain copy print ?
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4.   
  5. // Each notification type should gets its own enum  
  6. public enum NotificationType {  
  7.     OnStuff,  
  8.     OnOtherStuff,  
  9.     OnSomeEvent,  
  10.     TotalNotifications  
  11. };  
  12.   
  13. public delegate void OnNotificationDelegate( Notification note );  
  14.   
  15. public class NotificationCenter  
  16. {  
  17.     private static NotificationCenter instance;  
  18.   
  19.     private OnNotificationDelegate [] listeners = new OnNotificationDelegate[(int)NotificationType.TotalNotifications];  
  20.   
  21.     // Instead of constructor we can use void Awake() to setup the instance if we sublcass MonoBehavoiur  
  22.     public NotificationCenter()  
  23.     {  
  24.         if( instance != null )  
  25.         {  
  26.             Debug.Log( "NotificationCenter instance is not null" );  
  27.             return;  
  28.         }  
  29.         instance = this;  
  30.     }  
  31.       
  32.       
  33.     ~NotificationCenter()  
  34.     {  
  35.         instance = null;  
  36.     }  
  37.   
  38.   
  39.     public static NotificationCenter defaultCenter  
  40.     {  
  41.         get  
  42.         {  
  43.             if( instance == null )  
  44.                 new NotificationCenter();  
  45.             return instance;  
  46.         }  
  47.     }  
  48.   
  49.   
  50.     public void addListener( OnNotificationDelegate newListenerDelegate, NotificationType type )  
  51.     {  
  52.         int typeInt = (int)type;  
  53.         listeners[typeInt] += newListenerDelegate;  
  54.     }  
  55.   
  56.   
  57.     public void removeListener( OnNotificationDelegate listenerDelegate, NotificationType type )  
  58.     {  
  59.         int typeInt = ( int )type;  
  60.         listeners[typeInt] -= listenerDelegate;  
  61.     }  
  62.   
  63.   
  64.     public void postNotification( Notification note )  
  65.     {  
  66.         int typeInt = ( int )note.type;  
  67.   
  68.         if( listeners[typeInt] != null )  
  69.             listeners[typeInt](note);  
  70.     }  
  71.       
  72.   
  73. }  
  74.   
  75.   
  76.   
  77.   
  78. // Usage:  
  79. // NotificationCenter.defaultCenter.addListener( onNotification );  
  80. // NotificationCenter.defaultCenter.sendNotification( new Notification( NotificationTypes.OnStuff, this ) );  
  81. // NotificationCenter.defaultCenter.removeListener( onNotification, NotificationType.OnStuff );  

如上是NotificationCenter,它的工做呢就是:

1.一、建立多個監聽者,就至關於有多個類型的監聽器。 spa

[csharp]  view plain copy print ?
  1. OnNotificationDelegate [] listeners  

1.二、每一個監聽者可以「發送」屬於本身類型的方法便是通知。例若有一個「登陸按鈕」,這個按鈕有一個監聽者,那麼咱們就能夠經過Notification這個中心去向這個按鈕類型的監聽者註冊信息(或者移除監聽)。 .net

1.三、除了以上2點只是可以實現咱們前面說起的須要多個Delegate(其實就是建立多個監聽者類型這個數組),那麼咱們須要方法的傳參類型也不同呢? 3d

[csharp]  view plain copy print ?
  1. public delegate void OnNotificationDelegate( Notification note );  
咱們看到單個delegate的定義,傳參是Notification note;這裏建立了一個消息的基類;全部類型的監聽者[] 接收的傳參消息都要屬於單個Notification。

[csharp]  view plain copy print ?
  1. // Standard notification class.  For specific needs subclass  
  2. public class Notification  
  3. {  
  4.     public NotificationType type;  
  5.     public object userInfo;  
  6.   
  7.     public Notification( NotificationType type )  
  8.     {  
  9.         this.type = type;  
  10.     }  
  11.   
  12.   
  13.     public Notification( NotificationType type, object userInfo )  
  14.     {  
  15.         this.type = type;  
  16.         this.userInfo = userInfo;  
  17.     }  
  18. }  

如上圖,就是基本的通知內容體的類定義,若是要保存更復雜或者說更多類型的信息,那麼咱們能夠繼承這個消息基類。

[csharp]  view plain copy print ?
  1. public class SuperNotification : Notification  
  2. {  
  3.     public float varFloat;  
  4.     public int varInt;  
  5.   
  6.       
  7.     public SuperNotification( NotificationType type, float varFloat, int varInt ) : base( type )  
  8.     {  
  9.         this.varFloat = varFloat;  
  10.         this.varInt = varInt;  
  11.     }  
  12. }  

如上圖,就是繼承了基類Notification實現更多消息載體定義。能夠強制轉換類型傳給要調用的委託者,當該委託類型的方法接受到這個基類能夠從新轉化成它原來的類。

1.3.一、思考:若是使用Hashtable傳參呢?效率會不會快一點,並且咱們不須要太多的建立類。
1.3.二、思考:由於NotificationCenter中初始化這些監聽者數量就直接new數組,那麼假如我定義了不少這些監聽者類型。是否沒有使用到的監聽者就不須要建立了呢?把數組改爲List動態增長,效率是否會提升?
[csharp]  view plain copy print ?
  1. private OnNotificationDelegate [] listeners = new OnNotificationDelegate[(int)NotificationType.TotalNotifications];  
1.3.三、當切換場景時候必定要取消已經註冊的監聽者信息。

2、結合Notification的統一管理、範型傳參定義監聽方法的傳參數量分類、區分不一樣監聽者使用字符串名稱;參考:http://wiki.unity3d.com/index.php/Advanced_CSharp_Messenger  對象

這個結構製做的Message系統我以爲比前面的更增強大,並且不須要建立更多的消息內容體類,由於採用固定數量的範型傳參,通常來講傳參3個基本上就不少了足夠了,甚至能夠傳參就是前面咱們定義的Notification類,這就是範型的強大。

這裏就再也不闡述了,記住和前面的區別:

一、監聽者區分不用enum類型,而直接使用string名稱定義,經過鍵值對應儲存;

二、不用擔憂監聽的方法傳參問題,只須要區分是多少個參數來區分;

三、要支持自動切換場景時候進行這些監聽者的消除;


2.一、思考 :若是調用時候直接用string命名來區分不一樣的監聽者,那麼這樣很容易形成混亂,並且你代碼多了就不知道你前面定義的這個監聽者是什麼了。因此能夠統一固定一個enum區分或者監聽者身份類。



3、除了以上2個實現新的消息系統外,還有一種學習QT的消息槽結構來實現,參考:http://www.cocoachina.com/bbs/read.php?tid=68048&page=1 固然本人不熟悉QT的機制,稍微看了後,做爲一個區分來研究下。其實這種方式去實現delegate只是一種習慣了,還不能徹底是QT的消息槽機制。

[csharp]  view plain copy print ?
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class ZObject : MonoBehaviour {  
  5.        
  6.     public delegate void SIGNAL(Hashtable args);  
  7.       
  8.     public virtual void CONNECT(ref SIGNAL signal, SIGNAL slot){  
  9.         signal += slot;  
  10.     }  
  11.       
  12.     public virtual void DISCONNECT(ref SIGNAL signal, SIGNAL slot){  
  13.         signal -= slot;  
  14.     }  
  15.       
  16.     public void EMIT(SIGNAL signal, Hashtable args){  
  17.         if(signal != null)  
  18.             signal(args);  
  19.     }  
  20. }  
以上首先聲明一個消息槽機制的基礎對象,它必須能夠CONNECT和DISCONNECT、EMIT(發射信號);想象一下,有個信號源向天空發射了一枚煙花信號是救命的,而後不少我的(就是槽)就會接受到這個信號的內容而且調用。

咱們定義一個信號發射體,例如一個按鈕:

[csharp]  view plain copy print ?
  1. public class ClassA : ZObject {  
  2.       
  3.     public SIGNAL mouseClickSignal;  
  4.       
  5.     void OnMouseDown(){  
  6.         EMIT(mouseClickSignal, new Hashtable(){  
  7.             {"sender",      gameObject},  
  8.             {"position",    Vector3.one},  
  9.             {"string",      "hello world"},  
  10.             {"time",        Time.time}  
  11.         });   
  12.     }  
  13. }  

咱們看到,這個按鈕有一個點擊的信號,當點擊時候發射這個信號。

疑問:那麼誰要知道它的信號呢?就是-槽了。對,接下來只要把這個信號把須要獲取這個信號的槽鏈接起來,造成信號槽系統便可!

[csharp]  view plain copy print ?
  1. public class ControllerB : ZObject {  
  2.         
  3.     public ClassA objectA;  
  4.     public ClassA objectB;  
  5.     void Awake () {  
  6.           
  7.         CONNECT(ref objectA.mouseClickSignal, //sender's signal  
  8.                 SLOT_MouseClicked   //receiver's slot method  
  9.                 );   
  10.           
  11.         CONNECT(ref objectB.mouseClickSignal, //sender's signal  
  12.                 SLOT_MouseClicked   //receiver's slot method  
  13.                 );   
  14.           
  15.     }  
  16.       
  17.     //SLOT method   
  18.     //A slot is a function that is called in response to a particular signal  
  19.     void SLOT_MouseClicked(Hashtable args){  
  20.           
  21.         GameObject sender = (GameObject)args["sender"];  
  22.         string str = args["string"].ToString();  
  23.         float _time = (float)args["time"];  
  24.           
  25.         Debug.Log("RECEIVE SIGNAL : "+sender.name + " Say:"+str +" at:"+_time + " , call SLOT_MouseClicked");  
  26.     }  
  27.       
  28. }  

如上,咱們看到這個槽方法是:
[csharp]  view plain copy print ?
  1. void SLOT_MouseClicked(Hashtable args){  
咱們甚至還能夠新建不少個其餘槽方法,來鏈接這個信號便可。


3.一、思考,這個QT信號槽機制仍是很不錯的,很好理解。可是呢,若是切換場景的時候,要把這些信號給DISCONNECT掉就比較麻煩了。由於它們的信號有多是分散到各地,惟一要注意就是你在哪裏CONNECT了信號,就應該在這個COMPONENT裏面DISCONNECT掉它。


4、總結:

經過以上3點的C#支持的delegate消息系統強化後,能替代Unity3d的SendMessage這種消耗巨大的方式。


5、補充:

待續

相關文章
相關標籤/搜索