Last active
April 20, 2025 18:58
-
-
Save MBoffin/83a31982b278529e6edcb42d2b75b291 to your computer and use it in GitHub Desktop.
A very simple event management system for Unity.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Advanced C# messenger by Ilya Suzdalnitski. V1.0 | |
* | |
* Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended". | |
* (Modified by Dylan Bennett to add Unity 2017+ Scene Management support and switch to enums | |
* instead of strings for names of events.) | |
* | |
* 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>(Events.PropCollected, PropCollected); | |
Messenger.Broadcast<GameObject>(Events.PropCollected, prop); | |
2. Messenger.AddListener<float>(Events.SpeedChanged, SpeedChanged); | |
Messenger.Broadcast<float>(Events.SpeedChanged, 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; | |
using UnityEngine.SceneManagement; | |
public enum Events { | |
OnClick, | |
OnHover | |
} | |
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<Events, Delegate> eventTable = new Dictionary<Events, Delegate>(); | |
//Message handlers that should never be removed, regardless of calling Cleanup | |
static public List<Events> permanentMessages = new List<Events>(); | |
#endregion | |
#region Helper methods | |
//Marks a certain message as permanent. | |
static public void MarkAsPermanent(Events eventType) { | |
#if LOG_ALL_MESSAGES | |
Debug.Log("Messenger MarkAsPermanent \t\"" + eventType.ToString() + "\""); | |
#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<Events> messagesToRemove = new List<Events>(); | |
foreach (KeyValuePair<Events, Delegate> pair in eventTable) { | |
bool wasFound = false; | |
foreach (Events message in permanentMessages) { | |
if (pair.Key == message) { | |
wasFound = true; | |
break; | |
} | |
} | |
if (!wasFound) | |
messagesToRemove.Add(pair.Key); | |
} | |
foreach (Events message in messagesToRemove) { | |
eventTable.Remove(message); | |
} | |
} | |
static public void PrintEventTable() { | |
Debug.Log("\t\t\t=== MESSENGER PrintEventTable ==="); | |
foreach (KeyValuePair<Events, Delegate> pair in eventTable) { | |
Debug.Log("\t\t\t" + pair.Key.ToString() + "\t\t" + pair.Value); | |
} | |
Debug.Log("\n"); | |
} | |
#endregion | |
#region Message logging and exception throwing | |
static public void OnListenerAdding(Events eventType, Delegate listenerBeingAdded) { | |
#if LOG_ALL_MESSAGES || LOG_ADD_LISTENER | |
Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType.ToString() + "\"\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.ToString(), d.GetType().Name, listenerBeingAdded.GetType().Name)); | |
} | |
} | |
static public void OnListenerRemoving(Events eventType, Delegate listenerBeingRemoved) { | |
#if LOG_ALL_MESSAGES | |
Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType.ToString() + "\"\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.ToString())); | |
} | |
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.ToString(), 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.ToString())); | |
} | |
} | |
static public void OnListenerRemoved(Events eventType) { | |
if (eventTable[eventType] == null) { | |
eventTable.Remove(eventType); | |
} | |
} | |
static public void OnBroadcasting(Events 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.ToString())); | |
} | |
#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.ToString())); | |
} | |
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(Events eventType, Callback handler) { | |
OnListenerAdding(eventType, handler); | |
eventTable[eventType] = (Callback)eventTable[eventType] + handler; | |
} | |
//Single parameter | |
static public void AddListener<T>(Events eventType, Callback<T> handler) { | |
OnListenerAdding(eventType, handler); | |
eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler; | |
} | |
//Two parameters | |
static public void AddListener<T, U>(Events 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>(Events 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(Events eventType, Callback handler) { | |
OnListenerRemoving(eventType, handler); | |
eventTable[eventType] = (Callback)eventTable[eventType] - handler; | |
OnListenerRemoved(eventType); | |
} | |
//Single parameter | |
static public void RemoveListener<T>(Events eventType, Callback<T> handler) { | |
OnListenerRemoving(eventType, handler); | |
eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler; | |
OnListenerRemoved(eventType); | |
} | |
//Two parameters | |
static public void RemoveListener<T, U>(Events 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>(Events 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(Events 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.ToString() + "\""); | |
#endif | |
OnBroadcasting(eventType); | |
Delegate d; | |
if (eventTable.TryGetValue(eventType, out d)) { | |
Callback callback = d as Callback; | |
if (callback != null) { | |
callback(); | |
} | |
else { | |
throw CreateBroadcastSignatureException(eventType.ToString()); | |
} | |
} | |
} | |
//Single parameter | |
static public void Broadcast<T>(Events 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.ToString() + "\""); | |
#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.ToString()); | |
} | |
} | |
} | |
//Two parameters | |
static public void Broadcast<T, U>(Events 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.ToString() + "\""); | |
#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.ToString()); | |
} | |
} | |
} | |
//Three parameters | |
static public void Broadcast<T, U, V>(Events 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.ToString() + "\""); | |
#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.ToString()); | |
} | |
} | |
} | |
#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); | |
SceneManager.activeSceneChanged += OnSceneChange; | |
} | |
//Clean up eventTable every time a new level loads. | |
private void OnSceneChange(Scene prevScene, Scene curScene) { | |
if (prevScene.name != null) Messenger.Cleanup(); | |
} | |
} | |
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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment