首页 > 技术文章 > Unity3D之通过C#使用Advanced CSharp Messenger

ring1992 2016-10-24 17:08 原文

Advanced CSharp Messenger 属于C#事件的一种。 维基百科中由详细的说明http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger

Advanced CSharp Messenger的特点可以将游戏对象做为参数发送。到底Advanced CSharp Messenger有什么用呢?先创建一个立方体对象,然后把Script脚本绑定在这个对象中。脚本中有一个方法叫DoSomething()。写一段简单的代码,通常我们在调用方法的时候需要这样来写。

 1 private Script script;
 2 void Awake()
 3 {
 4     GameObject cube = GameObject.Find("Cube");
 5     script = cube.GetComponent<Script>();
 6 }
 7  
 8 void Update()
 9 {
10     if(Input.GetMouseButtonDown(0))
11     {
12         script.DoSomething();
13     }
14 }

代码比较简单,我就不注释了。 原理就是先获取游戏对象,接着获取脚本组件对象,最后通过脚本组件对象去调用对应脚本中的方法,这样的调用方法我们称之为直接调用。

这个例子中我只调用了一个对象的方法,如果说有成千上万个对象,那么这样调用是不是感觉自己的代码非常的丑?因为你需要一个一个的获取对象然后获取脚本组件然后在调用方法。。。。。 (想想都恐怖!!)

下面我们在用Advanced CSharp Messenger来实现事件的调用。按照维基百科中首先把Message.cs 和Callback.cs拷贝在你的工程中。

CallBack.cs

1 public delegate void Callback();
2 public delegate void Callback<T>(T arg1);
3 public delegate void Callback<T, U>(T arg1, U arg2);
4 public delegate void Callback<T, U, V>(T arg1, U arg2, V arg3);

 Message.cs

  1 /*
  2  * Advanced C# messenger by Ilya Suzdalnitski. V1.0
  3  * 
  4  * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
  5  * 
  6  * Features:
  7      * Prevents a MissingReferenceException because of a reference to a destroyed message handler.
  8      * Option to log all messages
  9      * Extensive error detection, preventing silent bugs
 10  * 
 11  * Usage examples:
 12      1. Messenger.AddListener<GameObject>("prop collected", PropCollected);
 13         Messenger.Broadcast<GameObject>("prop collected", prop);
 14      2. Messenger.AddListener<float>("speed changed", SpeedChanged);
 15         Messenger.Broadcast<float>("speed changed", 0.5f);
 16  * 
 17  * Messenger cleans up its evenTable automatically upon loading of a new level.
 18  * 
 19  * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string)
 20  * 
 21  */
 22  
 23 //#define LOG_ALL_MESSAGES
 24 //#define LOG_ADD_LISTENER
 25 //#define LOG_BROADCAST_MESSAGE
 26 #define REQUIRE_LISTENER
 27  
 28 using System;
 29 using System.Collections.Generic;
 30 using UnityEngine;
 31  
 32 static internal class Messenger {
 33     #region Internal variables
 34  
 35     //Disable the unused variable warning
 36 #pragma warning disable 0414
 37     //Ensures that the MessengerHelper will be created automatically upon start of the game.
 38     static private MessengerHelper messengerHelper = ( new GameObject("MessengerHelper") ).AddComponent< MessengerHelper >();
 39 #pragma warning restore 0414
 40  
 41     static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();
 42  
 43     //Message handlers that should never be removed, regardless of calling Cleanup
 44     static public List< string > permanentMessages = new List< string > ();
 45     #endregion
 46     #region Helper methods
 47     //Marks a certain message as permanent.
 48     static public void MarkAsPermanent(string eventType) {
 49 #if LOG_ALL_MESSAGES
 50         Debug.Log("Messenger MarkAsPermanent \t\"" + eventType + "\"");
 51 #endif
 52  
 53         permanentMessages.Add( eventType );
 54     }
 55  
 56     static public void Cleanup()
 57     {
 58 #if LOG_ALL_MESSAGES
 59         Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
 60 #endif
 61  
 62         List< string > messagesToRemove = new List<string>();
 63  
 64         foreach (KeyValuePair<string, Delegate> pair in eventTable) {
 65             bool wasFound = false;
 66  
 67             foreach (string message in permanentMessages) {
 68                 if (pair.Key == message) {
 69                     wasFound = true;
 70                     break;
 71                 }
 72             }
 73  
 74             if (!wasFound)
 75                 messagesToRemove.Add( pair.Key );
 76         }
 77  
 78         foreach (string message in messagesToRemove) {
 79             eventTable.Remove( message );
 80         }
 81     }
 82  
 83     static public void PrintEventTable()
 84     {
 85         Debug.Log("\t\t\t=== MESSENGER PrintEventTable ===");
 86  
 87         foreach (KeyValuePair<string, Delegate> pair in eventTable) {
 88             Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value);
 89         }
 90  
 91         Debug.Log("\n");
 92     }
 93     #endregion
 94  
 95     #region Message logging and exception throwing
 96     static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded) {
 97 #if LOG_ALL_MESSAGES || LOG_ADD_LISTENER
 98         Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType + "\"\t{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
 99 #endif
100  
101         if (!eventTable.ContainsKey(eventType)) {
102             eventTable.Add(eventType, null );
103         }
104  
105         Delegate d = eventTable[eventType];
106         if (d != null && d.GetType() != listenerBeingAdded.GetType()) {
107             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));
108         }
109     }
110  
111     static public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved) {
112 #if LOG_ALL_MESSAGES
113         Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType + "\"\t{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
114 #endif
115  
116         if (eventTable.ContainsKey(eventType)) {
117             Delegate d = eventTable[eventType];
118  
119             if (d == null) {
120                 throw new ListenerException(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", eventType));
121             } else if (d.GetType() != listenerBeingRemoved.GetType()) {
122                 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));
123             }
124         } else {
125             throw new ListenerException(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", eventType));
126         }
127     }
128  
129     static public void OnListenerRemoved(string eventType) {
130         if (eventTable[eventType] == null) {
131             eventTable.Remove(eventType);
132         }
133     }
134  
135     static public void OnBroadcasting(string eventType) {
136 #if REQUIRE_LISTENER
137         if (!eventTable.ContainsKey(eventType)) {
138             throw new BroadcastException(string.Format("Broadcasting message \"{0}\" but no listener found. Try marking the message with Messenger.MarkAsPermanent.", eventType));
139         }
140 #endif
141     }
142  
143     static public BroadcastException CreateBroadcastSignatureException(string eventType) {
144         return new BroadcastException(string.Format("Broadcasting message \"{0}\" but listeners have a different signature than the broadcaster.", eventType));
145     }
146  
147     public class BroadcastException : Exception {
148         public BroadcastException(string msg)
149             : base(msg) {
150         }
151     }
152  
153     public class ListenerException : Exception {
154         public ListenerException(string msg)
155             : base(msg) {
156         }
157     }
158     #endregion
159  
160     #region AddListener
161     //No parameters
162     static public void AddListener(string eventType, Callback handler) {
163         OnListenerAdding(eventType, handler);
164         eventTable[eventType] = (Callback)eventTable[eventType] + handler;
165     }
166  
167     //Single parameter
168     static public void AddListener<T>(string eventType, Callback<T> handler) {
169         OnListenerAdding(eventType, handler);
170         eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler;
171     }
172  
173     //Two parameters
174     static public void AddListener<T, U>(string eventType, Callback<T, U> handler) {
175         OnListenerAdding(eventType, handler);
176         eventTable[eventType] = (Callback<T, U>)eventTable[eventType] + handler;
177     }
178  
179     //Three parameters
180     static public void AddListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
181         OnListenerAdding(eventType, handler);
182         eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] + handler;
183     }
184     #endregion
185  
186     #region RemoveListener
187     //No parameters
188     static public void RemoveListener(string eventType, Callback handler) {
189         OnListenerRemoving(eventType, handler);   
190         eventTable[eventType] = (Callback)eventTable[eventType] - handler;
191         OnListenerRemoved(eventType);
192     }
193  
194     //Single parameter
195     static public void RemoveListener<T>(string eventType, Callback<T> handler) {
196         OnListenerRemoving(eventType, handler);
197         eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler;
198         OnListenerRemoved(eventType);
199     }
200  
201     //Two parameters
202     static public void RemoveListener<T, U>(string eventType, Callback<T, U> handler) {
203         OnListenerRemoving(eventType, handler);
204         eventTable[eventType] = (Callback<T, U>)eventTable[eventType] - handler;
205         OnListenerRemoved(eventType);
206     }
207  
208     //Three parameters
209     static public void RemoveListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
210         OnListenerRemoving(eventType, handler);
211         eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] - handler;
212         OnListenerRemoved(eventType);
213     }
214     #endregion
215  
216     #region Broadcast
217     //No parameters
218     static public void Broadcast(string eventType) {
219 #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
220         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
221 #endif
222         OnBroadcasting(eventType);
223  
224         Delegate d;
225         if (eventTable.TryGetValue(eventType, out d)) {
226             Callback callback = d as Callback;
227  
228             if (callback != null) {
229                 callback();
230             } else {
231                 throw CreateBroadcastSignatureException(eventType);
232             }
233         }
234     }
235  
236     //Single parameter
237     static public void Broadcast<T>(string eventType, T arg1) {
238 #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
239         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
240 #endif
241         OnBroadcasting(eventType);
242  
243         Delegate d;
244         if (eventTable.TryGetValue(eventType, out d)) {
245             Callback<T> callback = d as Callback<T>;
246  
247             if (callback != null) {
248                 callback(arg1);
249             } else {
250                 throw CreateBroadcastSignatureException(eventType);
251             }
252         }
253     }
254  
255     //Two parameters
256     static public void Broadcast<T, U>(string eventType, T arg1, U arg2) {
257 #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
258         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
259 #endif
260         OnBroadcasting(eventType);
261  
262         Delegate d;
263         if (eventTable.TryGetValue(eventType, out d)) {
264             Callback<T, U> callback = d as Callback<T, U>;
265  
266             if (callback != null) {
267                 callback(arg1, arg2);
268             } else {
269                 throw CreateBroadcastSignatureException(eventType);
270             }
271         }
272     }
273  
274     //Three parameters
275     static public void Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3) {
276 #if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
277         Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
278 #endif
279         OnBroadcasting(eventType);
280  
281         Delegate d;
282         if (eventTable.TryGetValue(eventType, out d)) {
283             Callback<T, U, V> callback = d as Callback<T, U, V>;
284  
285             if (callback != null) {
286                 callback(arg1, arg2, arg3);
287             } else {
288                 throw CreateBroadcastSignatureException(eventType);
289             }
290         }
291     }
292     #endregion
293 }
294  
295 //This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level.
296 public sealed class MessengerHelper : MonoBehaviour {
297     void Awake ()
298     {
299         DontDestroyOnLoad(gameObject);    
300     }
301  
302     //Clean up eventTable every time a new level loads.
303     public void OnDisable() {
304         Messenger.Cleanup();
305     }
306 }

然后就可以开始使用了,Messager.Broadcast()这样就好比我们发送了一条广播。

1     void Update()
2     {
3         if(Input.GetMouseButtonDown(0))
4         {
5             Messenger.Broadcast("Send");
6         }
7     }

在需要这条广播的类中来接受它,同样是刚刚说的Script类。接受广播的标志是 Messager.AddListener()参数1表示广播的名称,参数2表示广播所调用的方法。

 1 using UnityEngine;
 2 using System.Collections;
 3  
 4 public class Script : MonoBehaviour {
 5  
 6     void Awake()
 7     {
 8         Messenger.AddListener( "Send", DoSomething );
 9     }
10     public void DoSomething()
11     {
12         Debug.Log("DoSomething");
13     }
14 }

这样一来,只要发送名称为”Send”的方法,就可以在别的类中接收它了。

我们在说说如何通过广播来传递参数,这也是那天那个哥们主要问我的问题。(其实是维基百科上写的不是特别特别的清楚,那哥们误解了)在Callback中可以看出参数最多可以是三个,参数的类型是任意类型,也就是说我们不仅能传递 int float bool 还能传递gameObject类型。

如下所示,发送广播的时候传递了两个参数,参数1是一个游戏对象,参数2是一个int数值。

1     void Update()
2     {
3         if(Input.GetMouseButtonDown(0))
4         {
5             GameObject cube = GameObject.Find("Cube");
6             Messenger.Broadcast<GameObject,int>("Send",cube,1980);
7         }
8     }

然后是接受的地方 参数用<>存在一起。游戏对象也可以完美的传递。

 1 using UnityEngine;
 2 using System.Collections;
 3  
 4 public class Script : MonoBehaviour {
 5  
 6     void Awake()
 7     {
 8         Messenger.AddListener<GameObject,int>( "Send", DoSomething );
 9     }
10     public void DoSomething(GameObject obj,int i)
11     {
12         Debug.Log("name " + obj.name + " id =" + i);
13     }
14 }

如果传递一个参数<T>

两个参数<T,T>

三个参数<T,T,T>   

怎么样使用起来还是挺简单的吧?

我觉得项目中最好不要大量的使用代理事件这类的方法(根据需求而定),虽然可以让你的代码非常的简洁,但是它的效率不高大概比直接调用慢5-倍左右吧,就好比美好的东西一定都有瑕疵一样。 还记得Unity自身也提供了一种发送消息的方法吗?,用过的都知道效率也非常低下,虽然我们看不到它具体实现的源码是如何实现的,但是我觉得原理可能也是这样的。 欢迎和大家一起讨论与学习。

推荐阅读