Advanced CSharp Messenger 属于C#事件的一种。 维基百科中由详细的说明http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger 上周的一天刚巧有朋友问到我这一块的知识,那么我研究出来将它贴在博客中,帮助了他也帮助我自己!哇咔咔。
Advanced CSharp Messenger的特点可以将游戏对象做为参数发送。到底Advanced CSharp Messenger有什么用呢?先创建一个立方体对象,然后把Script脚本绑定在这个对象中。脚本中有一个方法叫DoSomething()。写一段简单的代码,通常我们在调用方法的时候需要这样来写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private Script script; void Awake() { GameObject cube = GameObject.Find("Cube"); script = cube.GetComponent<Script>(); } void Update() { if(Input.GetMouseButtonDown(0)) { script.DoSomething(); } } |
代码比较简单,我就不注释了。 原理就是先获取游戏对象,接着获取脚本组件对象,最后通过脚本组件对象去调用对应脚本中的方法,这样的调用方法我们称之为直接调用。
这个例子中我只调用了一个对象的方法,如果说有成千上万个对象,那么这样调用是不是感觉自己的代码非常的丑?因为你需要一个一个的获取对象然后获取脚本组件然后在调用方法。。。。。 (想想都恐怖!!)
下面我们在用Advanced CSharp Messenger来实现事件的调用。按照维基百科中首先把Message.cs 和Callback.cs拷贝在你的工程中。
CallBack.cs
1 2 3 4 5 6 |
public delegate void Callback(); public delegate void Callback<T>(T arg1); public delegate void Callback<T, U>(T arg1, U arg2); public delegate void Callback<T, U, V>(T arg1, U arg2, V arg3); |
Message.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
/* * Advanced C# messenger by Ilya Suzdalnitski. V1.0 * * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended". * * Features: * Prevents a MissingReferenceException because of a reference to a destroyed message handler. * Option to log all messages * Extensive error detection, preventing silent bugs * * Usage examples: 1. Messenger.AddListener<GameObject>("prop collected", PropCollected); Messenger.Broadcast<GameObject>("prop collected", prop); 2. Messenger.AddListener<float>("speed changed", SpeedChanged); Messenger.Broadcast<float>("speed changed", 0.5f); * * Messenger cleans up its evenTable automatically upon loading of a new level. * * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string) * */ //#define LOG_ALL_MESSAGES //#define LOG_ADD_LISTENER //#define LOG_BROADCAST_MESSAGE #define REQUIRE_LISTENER using System; using System.Collections.Generic; using UnityEngine; static internal class Messenger { #region Internal variables //Disable the unused variable warning #pragma warning disable 0414 //Ensures that the MessengerHelper will be created automatically upon start of the game. static private MessengerHelper messengerHelper = ( new GameObject("MessengerHelper") ).AddComponent< MessengerHelper >(); #pragma warning restore 0414 static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>(); //Message handlers that should never be removed, regardless of calling Cleanup static public List< string > permanentMessages = new List< string > (); #endregion #region Helper methods //Marks a certain message as permanent. static public void MarkAsPermanent(string eventType) { #if LOG_ALL_MESSAGES Debug.Log("Messenger MarkAsPermanent \t\"" + eventType + "\""); #endif permanentMessages.Add( eventType ); } static public void Cleanup() { #if LOG_ALL_MESSAGES Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed."); #endif List< string > messagesToRemove = new List<string>(); foreach (KeyValuePair<string, Delegate> pair in eventTable) { bool wasFound = false; foreach (string message in permanentMessages) { if (pair.Key == message) { wasFound = true; break; } } if (!wasFound) messagesToRemove.Add( pair.Key ); } foreach (string message in messagesToRemove) { eventTable.Remove( message ); } } static public void PrintEventTable() { Debug.Log("\t\t\t=== MESSENGER PrintEventTable ==="); foreach (KeyValuePair<string, Delegate> pair in eventTable) { Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value); } Debug.Log("\n"); } #endregion #region Message logging and exception throwing static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded) { #if LOG_ALL_MESSAGES || LOG_ADD_LISTENER Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType + "\"\t{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}"); #endif if (!eventTable.ContainsKey(eventType)) { eventTable.Add(eventType, null ); } Delegate d = eventTable[eventType]; if (d != null && d.GetType() != listenerBeingAdded.GetType()) { throw new ListenerException(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType().Name, listenerBeingAdded.GetType().Name)); } } static public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved) { #if LOG_ALL_MESSAGES Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType + "\"\t{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}"); #endif if (eventTable.ContainsKey(eventType)) { Delegate d = eventTable[eventType]; if (d == null) { throw new ListenerException(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", eventType)); } else if (d.GetType() != listenerBeingRemoved.GetType()) { throw new ListenerException(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name)); } } else { throw new ListenerException(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", eventType)); } } static public void OnListenerRemoved(string eventType) { if (eventTable[eventType] == null) { eventTable.Remove(eventType); } } static public void OnBroadcasting(string eventType) { #if REQUIRE_LISTENER if (!eventTable.ContainsKey(eventType)) { throw new BroadcastException(string.Format("Broadcasting message \"{0}\" but no listener found. Try marking the message with Messenger.MarkAsPermanent.", eventType)); } #endif } static public BroadcastException CreateBroadcastSignatureException(string eventType) { return new BroadcastException(string.Format("Broadcasting message \"{0}\" but listeners have a different signature than the broadcaster.", eventType)); } public class BroadcastException : Exception { public BroadcastException(string msg) : base(msg) { } } public class ListenerException : Exception { public ListenerException(string msg) : base(msg) { } } #endregion #region AddListener //No parameters static public void AddListener(string eventType, Callback handler) { OnListenerAdding(eventType, handler); eventTable[eventType] = (Callback)eventTable[eventType] + handler; } //Single parameter static public void AddListener<T>(string eventType, Callback<T> handler) { OnListenerAdding(eventType, handler); eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler; } //Two parameters static public void AddListener<T, U>(string eventType, Callback<T, U> handler) { OnListenerAdding(eventType, handler); eventTable[eventType] = (Callback<T, U>)eventTable[eventType] + handler; } //Three parameters static public void AddListener<T, U, V>(string eventType, Callback<T, U, V> handler) { OnListenerAdding(eventType, handler); eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] + handler; } #endregion #region RemoveListener //No parameters static public void RemoveListener(string eventType, Callback handler) { OnListenerRemoving(eventType, handler); eventTable[eventType] = (Callback)eventTable[eventType] - handler; OnListenerRemoved(eventType); } //Single parameter static public void RemoveListener<T>(string eventType, Callback<T> handler) { OnListenerRemoving(eventType, handler); eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler; OnListenerRemoved(eventType); } //Two parameters static public void RemoveListener<T, U>(string eventType, Callback<T, U> handler) { OnListenerRemoving(eventType, handler); eventTable[eventType] = (Callback<T, U>)eventTable[eventType] - handler; OnListenerRemoved(eventType); } //Three parameters static public void RemoveListener<T, U, V>(string eventType, Callback<T, U, V> handler) { OnListenerRemoving(eventType, handler); eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] - handler; OnListenerRemoved(eventType); } #endregion #region Broadcast //No parameters static public void Broadcast(string eventType) { #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\""); #endif OnBroadcasting(eventType); Delegate d; if (eventTable.TryGetValue(eventType, out d)) { Callback callback = d as Callback; if (callback != null) { callback(); } else { throw CreateBroadcastSignatureException(eventType); } } } //Single parameter static public void Broadcast<T>(string eventType, T arg1) { #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\""); #endif OnBroadcasting(eventType); Delegate d; if (eventTable.TryGetValue(eventType, out d)) { Callback<T> callback = d as Callback<T>; if (callback != null) { callback(arg1); } else { throw CreateBroadcastSignatureException(eventType); } } } //Two parameters static public void Broadcast<T, U>(string eventType, T arg1, U arg2) { #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\""); #endif OnBroadcasting(eventType); Delegate d; if (eventTable.TryGetValue(eventType, out d)) { Callback<T, U> callback = d as Callback<T, U>; if (callback != null) { callback(arg1, arg2); } else { throw CreateBroadcastSignatureException(eventType); } } } //Three parameters static public void Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3) { #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\""); #endif OnBroadcasting(eventType); Delegate d; if (eventTable.TryGetValue(eventType, out d)) { Callback<T, U, V> callback = d as Callback<T, U, V>; if (callback != null) { callback(arg1, arg2, arg3); } else { throw CreateBroadcastSignatureException(eventType); } } } #endregion } //This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level. public sealed class MessengerHelper : MonoBehaviour { void Awake () { DontDestroyOnLoad(gameObject); } //Clean up eventTable every time a new level loads. public void OnDisable() { Messenger.Cleanup(); } } |
然后就可以开始使用了,Messager.Broadcast()这样就好比我们发送了一条广播。
1 2 3 4 5 6 7 8 9 |
void Update() { if(Input.GetMouseButtonDown(0)) { Messenger.Broadcast("Send"); } } |
在需要这条广播的类中来接受它,同样是刚刚说的Script类。接受广播的标志是 Messager.AddListener()参数1表示广播的名称,参数2表示广播所调用的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using UnityEngine; using System.Collections; public class Script : MonoBehaviour { void Awake() { Messenger.AddListener( "Send", DoSomething ); } public void DoSomething() { Debug.Log("DoSomething"); } } |
这样一来,只要发送名称为”Send”的方法,就可以在别的类中接收它了。
我们在说说如何通过广播来传递参数,这也是那天那个哥们主要问我的问题。(其实是维基百科上写的不是特别特别的清楚,那哥们误解了)在Callback中可以看出参数最多可以是三个,参数的类型是任意类型,也就是说我们不仅能传递 int float bool 还能传递gameObject类型。
如下所示,发送广播的时候传递了两个参数,参数1是一个游戏对象,参数2是一个int数值。
1 2 3 4 5 6 7 8 9 10 |
void Update() { if(Input.GetMouseButtonDown(0)) { GameObject cube = GameObject.Find("Cube"); Messenger.Broadcast<GameObject,int>("Send",cube,1980); } } |
然后是接受的地方 参数用<>存在一起。游戏对象也可以完美的传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using UnityEngine; using System.Collections; public class Script : MonoBehaviour { void Awake() { Messenger.AddListener<GameObject,int>( "Send", DoSomething ); } public void DoSomething(GameObject obj,int i) { Debug.Log("name " + obj.name + " id =" + i); } } |
如果传递一个参数<T>
两个参数<T,T>
三个参数<T,T,T>
怎么样使用起来还是挺简单的吧?
我觉得项目中最好不要大量的使用代理事件这类的方法(根据需求而定),虽然可以让你的代码非常的简洁,但是它的效率不高大概比直接调用慢5-倍左右吧,就好比美好的东西一定都有瑕疵一样。 还记得Unity自身也提供了一种发送消息的方法吗?,用过的都知道效率也非常低下,虽然我们看不到它具体实现的源码是如何实现的,但是我觉得原理可能也是这样的。 欢迎和大家一起讨论与学习。
- 本文固定链接: https://www.xuanyusong.com/archives/2165
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
通俗易懂,解决了我半个月都没看懂的事,给你打钱了么么哒
已收到~
c#内置的事件系统会明确要求消息的目标,而且目标也同样要求知道消息的精确来源,在消息有广播需求时就会捉襟见肘了,这时候可以使用第三方的advanced c# messenger类(源码来自wiki库贡献,网上任意搜),它具有在消息传递沟通时非常好的解耦作用,对于传递消息时源任意,目标任意的情景比较适配。
这就是c#中的委托和事件,类似于c中函数指针~,效率不好会慢的~
效率不会慢的
MO哥,提个小建议,能给你的网站的代码部分加上一键复制代码的功能不,这样大家用起来会更方便一些。嘿嘿、、
MOMO这个和代理事件的效率比起来呢?也要慢吗?
可以把要传的参数封装成一个类,这样只需定义一个参数的委托。这样可以吗?
感觉用上去特别像 信号槽的 思想
我也经常用到这个,但是不会把AddListener 放到Awake()中去监听,一般会放在OnEnable()中去负责监听,最后在OnDisable()中使用RemoveListener()去除掉监听者。
SendMessage 是基于Reflection的 所以很慢
MOMO老师,这个,新手完全不清楚它用法啊….callback和Message到底合在一条脚本里?还是分开的?还有,脚本是挂在哪里的?是场景新建一个空物体吗,然后两条挂上去吗?
嘿,我就是问你的那个人,这是Advanced CSharp Messenger的用法:http://www.cnblogs.com/HelloUnity/archive/2013/06/18/AdvancedCSharpMessenger.html目前项目正在轰轰烈烈的使用中…
我倒觉得,SendMessage不会那么低。毕竟这个方法的执行是微秒级别的。就算是100倍的差异用户也几乎不会有任何感知。如果能够吧执行次数控制在 平均每秒10次内,就能用。
SendMessage 根据情况而用,
请教一下,这个慢5倍是怎么得出的?我不太会测
用循环测。。。 根据时间。。
方法的执行效率也是我最关注的,看来这种方法也是只能用在特殊场合了.
是的。。
学习了~
过奖了 蛤蛤。。
我想在自动寻路中是tank模型的炮管在转向,底座再随着转要怎么做呢?
求教下,我想在自动寻路时,tank模型的炮管先转向,底座再随着转向,要怎么做呢?
你把坦克分为两部分, 一部分是炮管 一部分是底座,然后通过修改旋转角度就可以做到。 以前我做过类似的。。。
我用得是两部分,但是怎么判定拐弯时一个先转,另外一个以一定角速度跟随转呢?谢谢指教
嗯,厉害厉害~
小伙子 很久没联系你了 呵呵。。
给你发消息你老不回好吧。。。
加油加油!!!
请问有下载吗。
比较简单我就没有提供下载地址 你按照流程写一遍就差不多了。。
都说委托事件快于unity的sendmessager,对此我也深信不疑,但sendmessage到底是基于何种机制呢?NGUI的很多事件消息的传送就直接用的是sendmessage,运行的速度感觉不到有多慢,挺纠结的,后来直接干脆两者混合着用,呵呵。
NGUI很多地方确实是用sendmessage 做的。。 它是为了避免耦合性。。 效率上肯定没有直接调用快。。
反射吧,我感觉
这方法挺好的,只是效率方面会怎样呢?
效率肯定没有直接调用快 。 但是最好不要通篇的用。。