轉載自 :http://wiki.unity3d.com/index.php?title=Advanced_CSharp_Messenger
先進CSharp信使
作者:Ilya Suzdalnitski
内容( 隐藏 ]
|
描述
這是一個先進的c#版本的消息傳遞系統。 它會自動清理事件表在一個新的水準加載。 這将防止程式員意外調用銷毀方法,進而有助于防止許多MissingReferenceExceptions。 這個消息傳遞系統是基于杆海德 CSharpMessenger 和馬格努斯Wolffelt CSharpMessenger擴充 。
前言
在消息傳遞系統引入我們的項目( CSharpMessenger擴充 )我們開始面臨非常奇怪的錯誤。 Unity3d會把MissingReferenceExceptions每次廣播消息。 它會說,類,消息處理程式中聲明,被毀。 問題從哪裡來的,沒有一個合理的解釋。 然而将消息處理程式代碼在try - catch塊解決了這個問題。 我們明白,這不是一個好的解決方案有成百上千的try - catch塊在我們的代碼。 我們花了一些時間來最後找出問題在哪裡。
MissingReferenceException背後的原因和解決方案
原來,MissingReferenceException錯誤出現了,當一個新的水準加載目前(或重新加載)。 例如,我們有一個消息 “開始遊戲” 宣布如下:
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");
}
}
乍一看,沒有問題,但是水準加載後,Unity3d将抛出一個異常,稱MainMenu已被摧毀。 然而沒有代碼,會破壞MainMenu腳本。
到底發生了什麼:
- 我們添加了一個“開始遊戲”對我們的信使消息偵聽器。
- StartGameButtonPressed叫反過來播放“開始遊戲”消息。
- 我們用Application.LoadLevel水準加載。
- 重複步驟1。
- 重複步驟2。
這裡有eventTable使者的樣子的相應的步驟:
- 在步驟1:{ “開始遊戲”,mainMenu1 - > StartGame(); }
- 在步驟4:{ “開始遊戲”,mainMenu1 - > StartGame(); } { “開始遊戲”,mainMenu2 - > StartGame(); }
是以在第四步我們有兩個相同的消息處理程式 “開始遊戲” 消息——第一個是摧毀MainMenu對象(被破壞時重新加載一個級别),而第二種它為目前有效MainMenu對象。 事實證明,當我們廣播的 “開始遊戲” 消息後重新加載水準,信使調用——摧毀和有效的消息處理程式。 這就是MissingReferenceException來自的地方。
是以,顯而易見——明确的解決方案 eventTable 卸貨後的水準。 沒有什麼别的程式員是他清理桌子,它是自動完成的。
的信使
我們很高興提供你一個進階版本的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 );
}
清理信使
信使清理其eventTable時自動加載一個新的水準。 這将確定eventTable信使的清理,将從意想不到的MissingReferenceExceptions拯救我們。 如果你想手動清理經理eventTable,有這樣一個選項通過調用Messenger.Cleanup();
永久的消息
如果你想要某個消息生存清理,馬克與Messenger.MarkAsPermanent(字元串)。 這可能需要如果某個類響應消息播放來自不同的水準。
Misc
記錄所有消息
用于調試目的,您可以設定 shouldLogAllMessages 國旗在信使為true。 這将記錄所有調用信使。
從其他使者
快速改變舊的CSharpMessenger所有調用消息傳遞系統的先進,做以下步驟:
- 在MonoDevelop去搜尋= >替換檔案
- 在找到字段輸入: 信使号<([^ < >]+)>([A-Za-z0-9_]+)。
- 在替換字段輸入: 信使。< $ 1 > 2美元
- 選擇範圍:整體解決方案。
- 檢查正規表達式搜尋”複選框。
- 按更換按鈕
代碼
有兩個檔案所需的信使——工作 Callback.cs 和 Messenger.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();
}
}