Whether you like to have reusability in mind, or just not want to hard-code any back-and-forth communication between classes, Event Dispatchers come quite useful.
There are multiple mechanisms for creating modular classes. Using Event Dispatchers is one of them. It allows you to anonymously bind a callback event, and more.
Let’s look at User Widget
It's fairly common to see a thoroughly decorated User Widget content hierarchy. Developers can even create and use their own type of User Widget for it. It’s possible to create reusable widget parts similar to button, text, check box, etc.
Notice how the engine is actively maintaining its modular structure everywhere? That’s how developers are able to constantly derive from or reuse existing classes. Likewise, we can recognize this modular structure and use it ourselves, which makes us innovate parallel to the engine rather than ‘on top’ of the engine in a more limiting nature.
What makes Event Dispatchers great for this?
Event Dispatchers are useful for maintaining a style similar to buttons: it becomes irrelevant what class is using me. The button doesn’t solely expect one particular type of class (except the User Widget base class) in any of its implementation, therefore, not limited to only being used in a certain type of parent environment.
Anonymous event binding
When you create a new Event Dispatcher in your User Widget, a green box automatically appears under "Events" in the details panel every time you use your new User Widget to decorate another User Widget’s content hierarchy. The same applies to Actor Components, which have the similar nature of being able to be added.
Pressing these green boxes will create the callback event for you. This is similar to just logically scripting a typical binding process of an Event Dispatcher to a custom event, except the engine neatly does that for you.
Instead of casting back and forth mysteriously between your custom widget’s children, arming your next child widget with Event Dispatchers will greatly improve encapsulation and your perception on what this widget will accomplish. Because ‘funneling’ some result through an Event Dispatcher call is a powerful summary. To break it down further: Any particular sequence of value changes that represents the state change of a high-level gameplay idea could be publicized through an Event Dispatcher call.
It becomes an independent, self-documenting building block similar to buttons.
Possibility for multiple callback events.
Event Dispatchers are not limited to just one single callback event binding. Anyone who is interested in being notified when it’s called can just bind, further encouraging event-driven approaches.
Theoretical usage example
Let's say you're making a sci-fi game. Players and other creatures can build and use autonomous fighter drones.
While there are many things to take into account, for this example, we're particularly interested in notifying the owner when the drone is:
- out of ammo.
- almost out of battery.
- dead. // Not equal to OnDestroyed
Staying in sync with the state of the drone is important if you're planning to do any (cosmetic) updates based on that. It could - for example - warn the player/AI-creature about something, resulting in a call-to-action.
Why we're using Event Dispatchers for this
There are many ways you can implement the requirements above.
There are many reasons to pick one approach over the other.
Here are the reasons why we're going for Event Dispatchers over interfaces or other means of inheritance:
- Following the nature of Event Dispatchers, anyone (if not marked private in the details panel) could at some point become interested in knowing about a particular state-change.
It's not mandatory to implement/inherit an interface, allowing a particular degree of runtime decision making freedom.
- There is no owner casting involved.
It's not implementation-wise bound to any type(/class), because we're not predetermining what particular member function will be invoked.
- To reduce general interface API overhead for loose gameplay elements like this. (Still however, personal preference)
If an enemy acknowledges a drone with its perception component, it might want to be notified when that drone dies in order to advance a certain strategy. (bind to Event Dispatcher).
And if the enemy's perception component loses the drone, it might want to unbind from any Event Dispatchers it previously bound to, because it's no longer relevant.
I think it's a great attempt to avoid having conditions evaluated every frame on Tick.
To sum it up
By adopting these mechanisms in order to create individual building blocks which can be used freely, we move away from a 'hard-code cast' dominant culture.