我们知道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 );
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; } }
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这种消耗巨大的方式。
五、补充:
待续