Unity中实现高效Signal-slot模式--高效消息分发
2016-04-01 10:32
477 查看
在项目中常常会用到观察者模式,消息分发这种功能。最简单的方式可以使用Unity自带的SendMessage来实现,但这种方式通过反射实现效率低下,并且消息接收者必须是GameObject类型,因此在实践中用到更多是的C#的delegate方式,但delegate实现并不能让消息的发送者和接收者解耦合。因此,我们需要一个消息的分发中心类来转发消息。发出消息的对象只需要知道这个消息中心类就行了,不需要关心有没有对象收到这个消息。
因此,我们的类是一个单例
为了高效地进行消息委托的查找,我们将消息以数组的方式存储起来。
首先定义方法的函数签名:
如果分发函数带返回值,那么再定义带返回值的签名
消息的发出:
订阅方调用:注意我们在调用的时候是给出了函数签名,这样在被强转为Delegate类型时函数签名信息才会被保存到Delegate对象中,最后在发消息将Delegate类型强转回来时才能正确地进行类型转换。按理说如果我用泛型多重载几个subscribe函数,listener的签名函数是可以被编译器自动推导的,但我尝试了不行(C++肯定是可以的)。因此很无奈,这里只能自己进行显示的签名转换。
发送方调用:幸好这里泛型给力,可以自动推导参数
有了这个基础类,项目中的消息分发就太容易了,而且效率和直接用Delegate几乎没有区别。
下面的全部的源码,供大家参考:
看到这里,让我想起了boost的function。
因此,我们的类是一个单例
public class SignalMgr { public static SignalMgr instance = new SignalMgr();
为了高效地进行消息委托的查找,我们将消息以数组的方式存储起来。
public enum GAME_EVT { // game common event GAME_START = 0, GAME_END, HUD_CAMERA_MOVE, MAX, } // define event name here public class SignalMgr { public static SignalMgr instance = new SignalMgr(); List<System.Delegate> _msgMap = null; public SignalMgr() { _msgMap = new List<Delegate>(); for (int i = 0; i < (int)GAME_EVT.MAX; ++i) { _msgMap.Add(null); } } public void Clear() { _msgMap.Clear(); }现在该是处理消息参数的时候了,常见的方式是将消息参数写成一个通用的类,将不同的参数转为object对象来传递。在handler中再将object转换为具体参数对象类似。这种方式因为存在装箱效率自然略差,另外如果参数传递较多,还会为不同的参数写一个包装结构体。为了避免这些问题,我采用了泛型传参。
首先定义方法的函数签名:
public delegate void funsig(); public delegate void funsig<T>(T t1); public delegate void funsig<T1, T2>(T1 t1, T2 t2); public delegate void funsig<T1, T2, T3>(T1 t1, T2 t2, T3 t3); public delegate void funsig<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4); public delegate void funsig<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
如果分发函数带返回值,那么再定义带返回值的签名
public delegate T funsigret<T>(); public delegate T funsigret<T, T1>(T1 t1); public delegate T funsigret<T, T1, T2>(T1 t1, T2 t2); public delegate T funsigret<T, T1, T2, T3>(T1 t1, T2 t2, T3 t3); public delegate T funsigret<T, T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4); public delegate T funsigret<T, T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);消息的订阅方法
public void Subscribe(GAME_EVT signal, Delegate listener) { if (listener == null) { return; } Delegate func = _msgMap[(int)signal]; if(func != null) { func = Delegate.Combine(func, listener); } else { func = listener; } _msgMap[(int)signal] = func; }
消息的发出:
public void Raise(GAME_EVT signal) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig tmp = (funsig)func; tmp(); } } public void Raise<T1>(GAME_EVT signal, T1 args) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1> tmp = (funsig<T1>)func; tmp(args); } } public void Raise<T1, T2>(GAME_EVT signal, T1 arg1, T2 arg2) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1, T2> tmp = (funsig<T1, T2>)func; tmp(arg1, arg2); } }
订阅方调用:注意我们在调用的时候是给出了函数签名,这样在被强转为Delegate类型时函数签名信息才会被保存到Delegate对象中,最后在发消息将Delegate类型强转回来时才能正确地进行类型转换。按理说如果我用泛型多重载几个subscribe函数,listener的签名函数是可以被编译器自动推导的,但我尝试了不行(C++肯定是可以的)。因此很无奈,这里只能自己进行显示的签名转换。
SignalMgr.instance.Subscribe (GAME_EVT.HUD_CAMERA_MOVE, (funsig<float, float>)OnCameraMove); void OnCameraMove (float deltaPixelX, float deltaPixelY) {}
发送方调用:幸好这里泛型给力,可以自动推导参数
SignalMgr.instance.Raise(GAME_EVT.HUD_CAMERA_MOVE, deltaPixelX, deltaPixelY);
有了这个基础类,项目中的消息分发就太容易了,而且效率和直接用Delegate几乎没有区别。
下面的全部的源码,供大家参考:
namespace signalModule { public delegate void funsig(); public delegate void funsig<T>(T t1); public delegate void funsig<T1, T2>(T1 t1, T2 t2); public delegate void funsig<T1, T2, T3>(T1 t1, T2 t2, T3 t3); public delegate void funsig<T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4); public delegate void funsig<T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); public delegate T funsigret<T>(); public delegate T funsigret<T, T1>(T1 t1); public delegate T funsigret<T, T1, T2>(T1 t1, T2 t2); public delegate T funsigret<T, T1, T2, T3>(T1 t1, T2 t2, T3 t3); public delegate T funsigret<T, T1, T2, T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4); public delegate T funsigret<T, T1, T2, T3, T4, T5>(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); public enum GAME_EVT { // game common event GAME_START = 0, GAME_END, HUD_CAMERA_MOVE, MAX, } // define event name here public class SignalMgr { public static SignalMgr instance = new SignalMgr(); List<System.Delegate> _msgMap = null; public SignalMgr() { _msgMap = new List<Delegate>(); for (int i = 0; i < (int)GAME_EVT.MAX; ++i) { _msgMap.Add(null); } } public void Dispose() { _msgMap.Clear(); } public void Subscribe(GAME_EVT signal, Delegate listener) { if (listener == null) { return; } Delegate func = _msgMap[(int)signal]; if(func != null) { func = Delegate.Combine(func, listener); } else { func = listener; } _msgMap[(int)signal] = func; } public void Unsubscribe(GAME_EVT signal, Delegate listener) { if (listener == null) { return; } Delegate func = _msgMap[(int)signal]; if(func != null) { func = Delegate.RemoveAll(func, listener); } _msgMap[(int)signal] = func; } public void Raise(GAME_EVT signal) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig tmp = (funsig)func; tmp(); } } public void Raise<T1>(GAME_EVT signal, T1 args) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1> tmp = (funsig<T1>)func; tmp(args); } } public void Raise<T1, T2>(GAME_EVT signal, T1 arg1, T2 arg2) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1, T2> tmp = (funsig<T1, T2>)func; tmp(arg1, arg2); } } public void Raise<T1, T2, T3>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1, T2, T3> tmp = (funsig<T1, T2, T3>)func; tmp(arg1, arg2, arg3); } } public void Raise<T1, T2, T3, T4>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1, T2, T3, T4> tmp = (funsig<T1, T2, T3, T4>)func; tmp(arg1, arg2, arg3, arg4); } } public void Raise<T1, T2, T3, T4, T5>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { Delegate func = _msgMap[(int)signal]; if(func != null) { funsig<T1, T2, T3, T4, T5> tmp = (funsig<T1, T2, T3, T4, T5>)func; tmp(arg1, arg2, arg3, arg4, arg5); } } public T RaiseRet<T>(GAME_EVT signal) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T> tmp = (funsigret<T>)func; return tmp(); } return new T(); } public T RaiseRet<T, T1>(GAME_EVT signal, T1 args) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T, T1> tmp = (funsigret<T, T1>)func; return tmp(args); } return new T(); } public T RaiseRet<T, T1, T2>(GAME_EVT signal, T1 arg1, T2 arg2) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T, T1, T2> tmp = (funsigret<T, T1, T2>)func; return tmp(arg1, arg2); } return new T(); } public T RaiseRet<T, T1, T2, T3>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T, T1, T2, T3> tmp = (funsigret<T, T1, T2, T3>)func; return tmp(arg1, arg2, arg3); } return new T(); } public T RaiseRet<T, T1, T2, T3, T4>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3, T4 arg4) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T, T1, T2, T3, T4> tmp = (funsigret<T, T1, T2, T3, T4>)func; return tmp(arg1, arg2, arg3, arg4); } return new T(); } public T RaiseRet<T, T1, T2, T3, T4, T5>(GAME_EVT signal, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) where T : new() { Delegate func = _msgMap[(int)signal]; if(func != null) { funsigret<T, T1, T2, T3, T4, T5> tmp = (funsigret<T, T1, T2, T3, T4, T5>)func; return tmp(arg1, arg2, arg3, arg4, arg5); } return new T(); } } }
看到这里,让我想起了boost的function。
相关文章推荐
- Unity实现相似于安卓原生项目的点击安卓返回button回到前一页的功能
- Unity3D 的摄像机
- Unity Shaders and Effects Cookbook (3-1) 使用Unity 内置的Specular Lighting - BlinnPhong
- 【Unity3D游戏开发】性能优化之Texture图片空间和内存占用分析(三七)
- 【Unity3D游戏开发】性能优化之如何将包大小减少到极致(三六)
- Unity 博客精选(持续更新)
- Thinking in Unity3D:基于物理着色(PBS)的材质系统
- unity androidSDK和IOS内购
- [Unity官方文档翻译]Import Settings unity资源中的导入设置
- 关于 Unity UI 中 GraphicRaycaster.Raycast 数量巨大的问题
- unity vs支持shader的插件
- NUGI中摄像机问题
- unity 链接 eclipse 调试
- Unity中透明材质物体渲染到RenderTexture的问题
- unity3d Physics
- 【unity小技巧之一】使Debug.Log支持颜色等富文本信息
- unity3d的playmaker插件使用教程,五、进入区域改变平台颜色
- unity 鼠标指示NavMeshAgent移动
- unity GPU优化
- Unity 之 Light : Light Probe