I've been trying to use events to decouple components wherever possible, and recently started reading more about "Event Manager" classes. The implementations I've been seeing however are solving one problem about decoupling but creating another. Let me give an example of some code I found online and my questions about it.
In this example an event is fired (using a static EventManager class) when coins are collected, and we have a producer of the event and a subscriber to it. The producer code:
public class Producer : MonoBehaviour {
void OnTriggerEnter2D(Collider2D other) {
EventManager.TriggerEvent("collectCoins", new Dictionary<string, object> { { "amount", 1 } });
}
}
And the consumer/subscriber:
public class Consumer : MonoBehaviour {
private int coins;
void OnEnable() {
EventManager.StartListening("collectCoins", OnCollectCoins);
}
void OnDisable() {
EventManager.StopListening("collectCoins", OnCollectCoins);
}
void OnCollectCoins(Dictionary<string, object> message) {
var amount = (int) message["amount"];
coins += amount;
}
}
Now let's say the consumer of the event is a UIManager which is a top level class quite specific to the individual game where we're not too worried about reusability. This will work fine and the dependency on the EventManager class probably isn't a big deal.
However for the producer of the event, if it's a "Collectable" class that I've written then I'd like that to be as independant as possible and be able to pop that in to another project without rewriting it. But I can't. Any project I bring the class to now needs a static EventManager class with the same method & parameters or it won't work.
If we look at the more vanilla style of setting up events without an EventManager I would usually do something like this:
public class Producer : MonoBehaviour
{
public static event Action<int> OnCoinsCollected;
void OnTriggerEnter2D(Collider2D other)
{
OnCoinsCollected?.Invoke(1);
}
}
Here we have a class that can be transported to another project very easily with no rework required.
When I first read about EventManagers, I assumed the purpose was for the EventManager to handle the subscribe/unsubscribe logic AND to trigger the event, meaning that both the producer and consumer don't know anything about each other and don't know anything about the EventManager. In my mind this is true separation and what decoupling is all about.
Would appreciate some advice on this. To put it in the form of a question - assuming an EventManager class is the best way to approach this problem, how should I build an EventManager class that can handle the invoking of events as well as the subscription side of things so that the classes beneath it have no knowledge of each other nor the EventManager?
Two-way decoupling like this can be achieved using dependency injection. A very simple example:
One possible approach to building such an event manager would be by creating an EditorWindow that allows linking arbitrary events to arbitrary methods using popups, and then have your event manager bind them together at runtime (perhaps with the help of a DI framework).
You could also use convention based binding. For example, you could automatically bind all methods using the naming convention "On{Type}{Name}" to static events named "{Name}" found on types named "{Type}" if their parameter lists are identical. A simple example: