unity简单设计模式---Advanced CSharp Messenger

内容

  • 1 描述
  • 2 前言
    • 2.1 MissingReferenceException 和解决方案背后的原因
  • 3 信使
    • 3.1 用法
      • 3.1.1 事件侦听器
      • 3.1.2 注册一个事件侦听器
      • 3.1.3 注销事件侦听器
      • 3.1.4 广播事件
    • 3.2 清理信使
      • 3.2.1 永久消息
    • 3.3 杂项
      • 3.3.1 记录所有消息
      • 3.3.2 从其他信使过渡
    • 3.4 代码
      • 3.4.1 Callback.cs
      • 3.4.2 Messenger.cs

描述

        这是一个C# 高级版本的消息传递系统 。它将会在加载一个新的level 后自动清理其事件表。这将防止程序员意外地调用被毁坏的方法,从而将有助于防止很多 MissingReferenceExceptions。此消息传递系统基于杆海德CSharpMessenger和马格努斯沃尔费尔特的CSharpMessenger 扩展.

前言

我们的项目 (CSharpMessenger 扩展) 的消息传递系统引入非常奇怪的 bug。每次播出一条消息,Unity3d 会抛出 MissingReferenceExceptions。它会说的类声明位置消息处理程序中,被毁坏了。但是放在 try catch 块内的消息处理程序代码解决这个问题。我们理解它不是一个好的解决方案,我们的代码中有数以百计的 try-catch 块。我们花了一些时间才终于弄明白这一问题在那里。

导致后面的 MissingReferenceException 和解决方案

原来,当一个新的level被加载 (或当前被重新加载),MissingReferenceException bug 出现了。例如,我们有一个消息"start game",声明如下:

public class MainMenu : MonoBehaviour {	
	void Start ()
	{
		Messenger.AddListener("start game", StartGame);
	}
 
	void StartGame()
	{
		Debug.Log("StartGame called in" + gameObject);  //This is the line that would throw an exception
	}
 
	void StartGameButtonPressed()
	{
		Messenger.Broadcast("start game");
	}
}

乍一看,就没有问题,但level已重新加载后,Unity3d 将引发异常,说 MainMenu 已被摧毁。

实际上发生的是:

  1. 我们向我们的信使Messenger添加"start game"的消息监听器。
  2. StartGameButtonPressed 被称为,反过来broadcasted 播出"start game"的消息。
  3. 我们重新加载与 Application.LoadLevel 的level。
  4. 重复步骤 1。
  5. 重复步骤 2。

这里是使者messenger 的如何 eventTable 在相应的步骤看起来像:

  • 在第 1 步: { "start game",mainMenu1-> StartGame() ; }
  • 在第 4 步: { "start game",mainMenu1-> StartGame() ; }{ "start game",mainMenu2-> StartGame() ; }

所以在第 4 步,我们有两个消息处理程序相同的"start game"消息 — — 第一个是被毁坏的 MainMenu 对象 (被摧毁时重新加载一个level,) 和第二个它为当前有效 MainMenu 对象。事实证明,当我们重新加载level后播出"start game"消息,使者调用-被毁和有效的消息处理程序。这是 MissingReferenceException 是从哪里来的。

所以解决方案是显而易见的 — — 一个level是 unloading 后清除eventTable 还有没有别的程序员都得在他的身边,清理一下table上做,它被自动完成。

信使

我们很高兴地给你一个高级版本的 C# 消息传递系统。

使用

事件侦听器

void OnPropCollected( PropType propType ) {
	if (propType == PropType.Life)
		livesAmount++;
}

 注册一个事件侦听器

void Start() {
	Messenger.AddListener< Prop >( "prop collected", OnPropCollected );
}

 取消注册事件侦听器

	Messenger.RemoveListener< Prop > ( "prop collected", OnPropCollected );

 广播事件

public void OnTriggerEnter(Collider _collider) 
{
	Messenger.Broadcast< PropType > ( "prop collected", _collider.gameObject.GetComponent<Prop>().propType );
}

清理信使messenger

信使自动清理其 eventTable 当加载一个新的level。这将确保信使 eventTable 获取清理,并将拯救我们从意想不到的 MissingReferenceExceptions。万一你想手动清理经理的 eventTable,还有这样一个选项通过调用 Messenger.Cleanup() ;

Permanent (常驻)的消息

如果你想要survive (幸存)的清理,用 Messenger.MarkAsPermanent(string) 标记某些消息。如果某一类响应在不同级别从播出的消息,这可能是必需的。


杂项

记录所有消息

出于调试目的,可以将在信使的shouldLogAllMessages标志设置为 true。这将记录所有调用的使者。

从其他信使的过渡

要快速更改为先进的所有调用的消息传递系统从旧 CSharpMessenger,请执行以下步骤:

  1. 在 MonoDevelop 去搜索 = > 在文件中替换
  2. 在查找字段中输入:Messenger<([^<>]+)>.([A-Za-z0-9_]+) 
  3. 在替换字段中输入:Messenger.$2<$1> 
  4. 选择范围: 整个解决方案。
  5. 请检查该正则表达式搜索复选框。
  6. 按Replace 替换按钮

代码

有两个文件所需的工作 — — Callback.csMessenger.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);

Messenger.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("\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 OnLevelWasLoaded(int unused) {
		Messenger.Cleanup();
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值