Advanced CSharp Messenger 属于C#事件的一种。 维基百科中由详细的说明http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger 上周的一天刚巧有朋友问到我这一块的知识,那么我研究出来将它贴在博客中,帮助了他也帮助我自己!哇咔咔。
Advanced CSharp Messenger的特点可以将游戏对象做为参数发送。到底Advanced CSharp Messenger有什么用呢?先创建一个立方体对象,然后把Script脚本绑定在这个对象中。脚本中有一个方法叫DoSomething()。写一段简单的代码,通常我们在调用方法的时候需要这样来写。
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
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
/*
* 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("ttt=== MESSENGER PrintEventTable ===");
foreach (KeyValuePair<string, Delegate> pair in eventTable) {
Debug.Log("ttt" + pair.Key + "tt" + 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("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking 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("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking 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("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking 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("MESSENGERt" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "tttInvoking 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()这样就好比我们发送了一条广播。
void Update()
{
if(Input.GetMouseButtonDown(0))
{
Messenger.Broadcast("Send");
}
}
在需要这条广播的类中来接受它,同样是刚刚说的Script类。接受广播的标志是 Messager.AddListener()参数1表示广播的名称,参数2表示广播所调用的方法。
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数值。
void Update()
{
if(Input.GetMouseButtonDown(0))
{
GameObject cube = GameObject.Find("Cube");
Messenger.Broadcast<GameObject,int>("Send",cube,1980);
}
}
然后是接受的地方 参数用<>存在一起。游戏对象也可以完美的传递。
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 做的。。 它是为了避免耦合性。。 效率上肯定没有直接调用快。。
反射吧,我感觉
这方法挺好的,只是效率方面会怎样呢?
效率肯定没有直接调用快 。 但是最好不要通篇的用。。