我們知道Unity3D自身有SendMessage向對象之間發送消息,但這個消耗是比較大的,因爲它很大程度上涉及了Reflection發射機制。
如何變更思路,結合C#自帶的消息系統delegate委託事件,對此進行優化:
我們看以下一個簡單的delegate使用:
public class DelegateBasic : MonoBehaviour {
//define my delegate statement.
public delegate void MyDelegate(string arg1);
//create my delegate object
public MyDelegate myDelegate;
// Use this for initialization
void Start () {
myDelegate += myFunciton1;
myDelegate += myFunciton2;
}
// Update is called once per frame
void Update () {
}
void OnGUI()
{
if(GUILayout.Button("INVOKE"))
{
myDelegate("Invoke....");
}
}
void myFunciton1(string s)
{
Debug.Log("myFunciton1 " + s);
}
void myFunciton2(string s)
{
Debug.Log("myFunciton2 " + s);
}
}
以上只是實現myDelegate一旦調用,那麼連接了該委託的事件的方法都會被調用。
但是以上我們看到,這只是1個delegate類型對應 —— 多個與該委託定義形式一致的方法;假如我需要不同的delegate呢?而且這些方法形式定義不一樣,也或者說我們需要方法傳參不一樣呢?
一、Notification通知中心結構:參考http://wiki.unity3d.com/index.php/Category:Messaging 這裏有很多個寫好的例子可以使用。
using UnityEngine;
using System.Collections;
// Each notification type should gets its own enum
public enum NotificationType {
OnStuff,
OnOtherStuff,
OnSomeEvent,
TotalNotifications
};
public delegate void OnNotificationDelegate( Notification note );
public class NotificationCenter
{
private static NotificationCenter instance;
private OnNotificationDelegate [] listeners = new OnNotificationDelegate[(int)NotificationType.TotalNotifications];
// Instead of constructor we can use void Awake() to setup the instance if we sublcass MonoBehavoiur
public NotificationCenter()
{
if( instance != null )
{
Debug.Log( "NotificationCenter instance is not null" );
return;
}
instance = this;
}
~NotificationCenter()
{
instance = null;
}
public static NotificationCenter defaultCenter
{
get
{
if( instance == null )
new NotificationCenter();
return instance;
}
}
public void addListener( OnNotificationDelegate newListenerDelegate, NotificationType type )
{
int typeInt = (int)type;
listeners[typeInt] += newListenerDelegate;
}
public void removeListener( OnNotificationDelegate listenerDelegate, NotificationType type )
{
int typeInt = ( int )type;
listeners[typeInt] -= listenerDelegate;
}
public void postNotification( Notification note )
{
int typeInt = ( int )note.type;
if( listeners[typeInt] != null )
listeners[typeInt](note);
}
}
// Usage:
// NotificationCenter.defaultCenter.addListener( onNotification );
// NotificationCenter.defaultCenter.sendNotification( new Notification( NotificationTypes.OnStuff, this ) );
// NotificationCenter.defaultCenter.removeListener( onNotification, NotificationType.OnStuff );
如上是NotificationCenter,它的工作呢就是:
1.1、創建多個監聽者,就相當於有多個類型的監聽器。
OnNotificationDelegate [] listeners
1.2、每個監聽者能夠“發送”屬於自己類型的方法即是通知。例如有一個“登錄按鈕”,這個按鈕有一個監聽者,那麼我們就可以通過Notification這個中心去向這個按鈕類型的監聽者註冊信息(或者移除監聽)。
1.3、除了以上2點只是能夠實現我們前面提及的需要多個Delegate(其實就是創建多個監聽者類型這個數組),那麼我們需要方法的傳參類型也不一樣呢?
public delegate void OnNotificationDelegate( Notification note );
// Standard notification class. For specific needs subclass
public class Notification
{
public NotificationType type;
public object userInfo;
public Notification( NotificationType type )
{
this.type = type;
}
public Notification( NotificationType type, object userInfo )
{
this.type = type;
this.userInfo = userInfo;
}
}
如上圖,就是基本的通知內容體的類定義,如果要保存更復雜或者說更多類型的信息,那麼我們可以繼承這個消息基類。
public class SuperNotification : Notification
{
public float varFloat;
public int varInt;
public SuperNotification( NotificationType type, float varFloat, int varInt ) : base( type )
{
this.varFloat = varFloat;
this.varInt = varInt;
}
}
如上圖,就是繼承了基類Notification實現更多消息載體定義。可以強制轉換類型傳給要調用的委託者,當該委託類型的方法接受到這個基類可以重新轉化成它原來的類。
private OnNotificationDelegate [] listeners = new OnNotificationDelegate[(int)NotificationType.TotalNotifications];
二、結合Notification的統一管理、範型傳參定義監聽方法的傳參數量分類、區分不同監聽者使用字符串名稱;參考:http://wiki.unity3d.com/index.php/Advanced_CSharp_Messenger
這個結構製作的Message系統我覺得比前面的更加強大,而且不需要創建更多的消息內容體類,因爲採用固定數量的範型傳參,一般來說傳參3個基本上就很多了足夠了,甚至可以傳參就是前面我們定義的Notification類,這就是範型的強大。
這裏就不再闡述了,記住和前面的區別:
1、監聽者區分不用enum類型,而直接使用string名稱定義,通過鍵值對應儲存;
2、不用擔心監聽的方法傳參問題,只需要區分是多少個參數來區分;
3、要支持自動切換場景時候進行這些監聽者的消除;
2.1、思考 :如果調用時候直接用string命名來區分不同的監聽者,那麼這樣很容易造成混亂,而且你代碼多了就不知道你前面定義的這個監聽者是什麼了。所以可以統一固定一個enum區分或者監聽者身份類。
三、除了以上2個實現新的消息系統外,還有一種學習QT的消息槽結構來實現,參考:http://www.cocoachina.com/bbs/read.php?tid=68048&page=1 當然本人不熟悉QT的機制,稍微看了後,作爲一個區分來研究下。其實這種方式去實現delegate只是一種習慣了,還不能完全是QT的消息槽機制。
using UnityEngine;
using System.Collections;
public class ZObject : MonoBehaviour {
public delegate void SIGNAL(Hashtable args);
public virtual void CONNECT(ref SIGNAL signal, SIGNAL slot){
signal += slot;
}
public virtual void DISCONNECT(ref SIGNAL signal, SIGNAL slot){
signal -= slot;
}
public void EMIT(SIGNAL signal, Hashtable args){
if(signal != null)
signal(args);
}
}
以上首先聲明一個消息槽機制的基礎對象,它必須可以CONNECT和DISCONNECT、EMIT(發射信號);想象一下,有個信號源向天空發射了一枚煙花信號是救命的,然後很多個人(就是槽)就會接受到這個信號的內容並且調用。
我們定義一個信號發射體,例如一個按鈕:
public class ClassA : ZObject {
public SIGNAL mouseClickSignal;
void OnMouseDown(){
EMIT(mouseClickSignal, new Hashtable(){
{"sender", gameObject},
{"position", Vector3.one},
{"string", "hello world"},
{"time", Time.time}
});
}
}
我們看到,這個按鈕有一個點擊的信號,當點擊時候發射這個信號。
疑問:那麼誰要知道它的信號呢?就是-槽了。對,接下來只要把這個信號把需要獲取這個信號的槽連接起來,形成信號槽系統即可!
public class ControllerB : ZObject {
public ClassA objectA;
public ClassA objectB;
void Awake () {
CONNECT(ref objectA.mouseClickSignal, //sender's signal
SLOT_MouseClicked //receiver's slot method
);
CONNECT(ref objectB.mouseClickSignal, //sender's signal
SLOT_MouseClicked //receiver's slot method
);
}
//SLOT method
//A slot is a function that is called in response to a particular signal
void SLOT_MouseClicked(Hashtable args){
GameObject sender = (GameObject)args["sender"];
string str = args["string"].ToString();
float _time = (float)args["time"];
Debug.Log("RECEIVE SIGNAL : "+sender.name + " Say:"+str +" at:"+_time + " , call SLOT_MouseClicked");
}
}
如上,我們看到這個槽方法是:
void SLOT_MouseClicked(Hashtable args){
我們甚至還可以新建很多個其他槽方法,來連接這個信號即可。
3.1、思考,這個QT信號槽機制還是很不錯的,很好理解。但是呢,如果切換場景的時候,要把這些信號給DISCONNECT掉就比較麻煩了。因爲它們的信號有可能是分散到各地,唯一要注意就是你在哪裏CONNECT了信號,就應該在這個COMPONENT裏面DISCONNECT掉它。
四、總結:
通過以上3點的C#支持的delegate消息系統強化後,能替代Unity3d的SendMessage這種消耗巨大的方式。
五、補充:
待續