Generic Event Bus In Unity

Habibur Rahman Ovie
4 min readMar 4, 2023

--

An event bus is a pipeline of events where the publisher triggers an event and subscribers receive that event and act accordingly. We can imagine it as having a sudden burst of a radio signal that only those with antennas tuned to a specific frequency can detect the message.
For example, in a game, it is very important to know when a game started, when it ended, when and where the player dies, etc. Not only game it is one of the key mechanisms in any software.

What will we build — Here I will try to create a robust and low-coupling event bus mechanism for triggering and detecting events with or without values. The main focus will be to make it super simple to use.

  1. First, we create an enum class for holding all of the event-type data.
  2. Then we will create the brain of our mechanism where all of our listening and triggering functions will exist.
  3. Lastly, we will just call the register and listener functions whenever we need them.

Create the Enum —
It is a simple class that holds some enum values of event-type data.

public class EventBusEnum 
{
public enum EventName
{
START_GAME,
SCORE_UPDATE,
THEME_CHANGE
}
}

Create the Brain —
EBM class is the brain of this mechanism. This is a singleton class so we can call this without making any object of this class.

      Hashtable eventHash = new Hashtable();

private static EBM ebm;

public static EBM instance
{
get
{
if (!ebm)
{
ebm = FindObjectOfType(typeof(EBM)) as EBM;

if (!ebm)
{
Debug.LogError("There needs to be one active EBM script on a GameObject in your scene.");
}
else
{
ebm.Init();
}
}

return ebm;
}
}

void Init()
{
if (ebm.eventHash == null)
{
ebm.eventHash = new Hashtable();
}
}

First, we make the EBM instance. This code block is pretty self-explanatory and very common in singleton patterns. Then we use a HashTable to store all of our event data. Like Dictionary hashTable is a key value pair but in a hashTable, you do not have to declare the value type. As we want to return value from an event and that value can be any type so we make it generic and use hashTable.

Start Listening -

public static void StartListening<T>(EventBusEnum.EventName eventName, UnityAction<T> listener)
{
UnityEvent<T> thisEvent = null;

string b = GetKey<T>(eventName);

if (instance.eventHash.ContainsKey(b))
{
thisEvent = (UnityEvent<T>)instance.eventHash[b];
thisEvent.AddListener(listener);
instance.eventHash[eventName] = thisEvent;
}
else
{
thisEvent = new UnityEvent<T>();
thisEvent.AddListener(listener);
instance.eventHash.Add(b, thisEvent);

}
}

It is a generic function that will store the event's action in our hashtable. As we want to return value from the event and our return type can be anything so, we use generic <T>. This means T can be any type and UnityAction’s type will be T. In the next step, I create the hashtable key by adding the type and event name. I do it because I want to separate the different ActionType with the same EventName. You will get a better understanding of our use cases.

private static string GetKey<T>(EventBusEnum.EventName eventName)
{
Type type = typeof(T);
string key = type.ToString() + eventName.ToString();
return key;
}

If you do not want to return anything then you can simply write the following code.

public static void StartListening(EventBusEnum.EventName eventName, UnityAction listener)
{
UnityEvent thisEvent = null;

// string b = GetKey<T>(eventName);

if (instance.eventHash.ContainsKey(eventName))
{
thisEvent = (UnityEvent)instance.eventHash[eventName];
thisEvent.AddListener(listener);
instance.eventHash[eventName] = thisEvent;
}
else
{
thisEvent = new UnityEvent();
thisEvent.AddListener(listener);
instance.eventHash.Add(eventName, thisEvent);

}
}

Stop Listening —

public static void StopListening<T>(EventBusEnum.EventName eventName, UnityAction<T> listener)
{
if (ebm == null) return;
UnityEvent<T> thisEvent = null;
string key = GetKey<T>(eventName);
if (instance.eventHash.ContainsKey(key))
{
thisEvent = (UnityEvent<T>)instance.eventHash[key];
thisEvent.RemoveListener(listener);
instance.eventHash[eventName] = thisEvent;
}
}

The above code is self-explanatory. When you want to stop listening from some script then you have to call the above function. Here we remove the listener from that event.

Trigger Event —

public static void TriggerEvent<T>(EventBusEnum.EventName eventName,T val)
{
UnityEvent<T> thisEvent = null;
string key = GetKey<T>(eventName);
if (instance.eventHash.ContainsKey(key))
{
thisEvent = (UnityEvent<T>)instance.eventHash[key];
thisEvent.Invoke(val);
}
}

Use Cases —

//Trigger 
EBM.TriggerEvent<THEME>(EventBusEnum.EventName.THEME_CHANGE, THEME.WHITE);
//Start Listening
EBM.StartListening<THEME>(EventBusEnum.EventName.THEME_CHANGE, OnThemeChanged);
//Stop Listening
EBM.StopListening<THEME>(EventBusEnum.EventName.THEME_CHANGE, OnThemeChanged);

void OnThemeChanged(THEME theme)
{
if (text == null)
{
return;
}

switch (theme)
{
case THEME.WHITE:
text.color = Color.black;
break;
case THEME.DARK:
text.color = Color.white;
break;
case THEME.BLUE:
text.color = Color.red;
break;
}
}

Just write the above codes in your desired classes. It is highly recommended to put the start listening call in OnEnable and put the stop listening in the OnDisable function.

I hope this will help you to make your next project more organized and clean.

Check the full project from GitHub
Please support me if you can and think it is valuable to you.

--

--