Event Dispatchers: How to Announce Something Without Knowing Who Cares

When something happens and several systems need to react, wiring each one by hand is how you build a knot.

An Event Dispatcher lets an actor shout "this happened" and lets anyone listen.

Here is how they work, when to use one instead of an interface, and the hard-reference caveat everyone trips on.

BRIEFING

An Event Dispatcher is the Observer pattern in Blueprint: an actor owns a named event, other actors bind to it, and when the owner broadcasts it, every listener reacts, without the owner knowing or referencing any of them. It is the right tool when one thing happens and many, unknown, things must respond. This piece covers how dispatchers work, bind and unbind and call, when to pick a dispatcher over an interface, the caveat that binding still needs a reference to the sender, and the honest performance picture, including why event-driven beats polling on Tick.

Consider a single moment: the player takes a hit. The health bar has to drop, a sound has to play, the screen has to flash, nearby enemies should notice, and an achievement might tick over. If your health logic calls each of those systems directly, it now holds a reference to the UI, the audio, the AI and the achievement manager, and your health component, which should only know about health, is suddenly wired to half the game. Add one more reaction later and you are back editing the health code.

There is a way to turn that around completely, so the health component announces one thing and never learns who listened. That is the Event Dispatcher.

What an Event Dispatcher actually is

An Event Dispatcher is a named event that a Blueprint owns and can broadcast. Other Blueprints bind their own functions to it, and when the owner calls the dispatcher, every bound function runs. The essential property is that the owner holds no references to the listeners at all. It just shouts.

This is the classic Observer pattern: a subject announces a change, and any number of observers, added and removed freely, react to it. Your health component owns an OnHealthChanged dispatcher and broadcasts it whenever health moves; the UI, the audio and the AI each bound to it, and the component never had to know they exist.

Bind, unbind, call

Three verbs run a dispatcher's whole life. A listener binds a function to the dispatcher, usually when it starts up or when it first gets access to the sender.

The owner calls (broadcasts) the dispatcher when the event happens, optionally passing parameters along, such as the new health value. And a listener should unbind when it no longer cares, or when it is about to be destroyed, to avoid a stale binding pointing at something that is going away. Note one deliberate limitation: a dispatcher can pass data out to its listeners, but it cannot collect a value back. It is a one-way announcement, not a question.

Why it decouples

The win is that the sender depends on nobody. You can add a fourth and a fifth reaction to "health changed" months later, and you never open the health component to do it; the new listeners simply bind.

Communication flows one-to-many, from a single event out to however many systems care, and the direction of knowledge points the right way: the small, low-level thing (the component) announces, and the higher-level things (UI, AI) choose to listen. It is the natural tool for a child or an owned object to tell the wider world "I happened" without reaching up and grabbing it.

The caveat everyone trips on

Here is the honest catch, and it surprises people who expect dispatchers to be pure magic. The sender does not need to know its listeners, but a listener does need a reference to the sender in order to bind to its dispatcher.

Dispatchers decouple the reaction, not always the initial wiring. In practice this is usually fine, because you bind where you already hold the reference: a component binding to its own owning actor, an actor binding to a child it spawned, a spawner binding to the enemies it just created, a widget binding to the specific component it displays.

When even that reference is undesirable, when you want a sender and listeners that know nothing about each other at all, you move up to a central hub: a GameInstance Subsystem or a message router that both sides can reach by type. Dispatchers are the right tool for direct owner-to-listener events; a subsystem is the tool for fully anonymous, project-wide messages.

When to reach for a dispatcher

Use a dispatcher when the shape of the problem is one event, many possibly-unknown reactions:

 

  • Something happened and you do not want to hardcode who reacts: OnHealthChanged, OnDied, OnPickedUp, OnObjectiveComplete. Let whoever cares subscribe.

 

  • Bottom-up notifications: a button telling the room it was pressed, an enemy telling its spawner it died, a component telling its actor a threshold was crossed.

 

  • Keeping a low-level system ignorant of high-level ones: the health component should never reference the UI. It broadcasts; the UI listens.

Dispatcher, interface, direct call or subsystem? Choosing well

Dispatcher vs Interface

This is the decision people get stuck on, so here it is plainly. An interface is a directed call: you reach out to a target you already reference and say "you, do this," one-to-one, and it can hand a value back. A dispatcher is a broadcast: you announce "this happened" and unknown listeners react, one-to-many, with no value returned. The rule of thumb writes itself: if you know exactly who you want to talk to, use an interface; if you do not know or care who cares, use a dispatcher. You hit a specific enemy with an interface. You announce that you died with a dispatcher. Many good systems use both: an interface to do the thing, a dispatcher to tell the world it was done.

Dispatcher vs a direct call, and vs polling

The alternative to a dispatcher is usually one of two worse things. Either the sender calls each listener directly, which is exactly the tight coupling we are escaping, or listeners sit on Tick asking "did it change yet? did it change yet?" every single frame. The dispatcher replaces both with a clean, event-driven push: nothing runs until the event actually fires, which is better architecture and, as it happens, better performance.

That last point is worth pressing, because it is the real performance story here. Reacting to an event only when it happens is dramatically cheaper than checking for it every frame, which is the whole case for getting logic off Event Tick. The CPU cost of a dispatcher itself is negligible, scaling only with how many listeners are bound, and it saves you the far larger cost of polling. None of this needs C++, by the way; it is all standard Blueprint, as I argued in the C++ myth piece.

KEY TAKEAWAY

When one event needs many reactions, do not wire them by hand. Let the sender own a dispatcher and broadcast, and let listeners subscribe.

The sender stays ignorant of everyone, you add reactions without touching it, and you replace per-frame polling with a clean push. Just remember: the listener still needs a reference to the sender to bind.

WATCH OUT

Two traps. First, unbind when a listener is done or being destroyed; stale bindings that point at a departing object are a classic source of subtle bugs.

Second, do not force a dispatcher where a directed interface call is clearer: broadcasting "please, specific door, open" to the whole world is backwards. Dispatchers are for genuine one-to-many events, not a way to avoid ever holding a reference.

Glossary

Event Dispatcher.  a named event a Blueprint owns and broadcasts; other Blueprints bind functions to it and react when it fires. Blueprint's form of the Observer pattern.

Observer pattern.  a design where a subject announces changes and any number of observers react, added and removed freely, without the subject referencing them.

Bind / Unbind.  subscribing a function to a dispatcher, and later removing it (do this when a listener is destroyed to avoid stale bindings).

Call (broadcast).  firing a dispatcher so every bound listener runs, optionally with parameters passed out.

One-to-many.  one event reaching many listeners at once, the shape a dispatcher is built for.

Directed call.  the interface alternative: reaching out to one known target rather than broadcasting to unknown listeners.

Polling.  repeatedly checking for a change every frame on Tick; what an event-driven dispatcher lets you stop doing.

Subsystem message hub.  a central object both sender and listeners reach by type, for fully anonymous communication when even a reference to the sender is undesirable.

Systems reaching into each other until nothing is safe to change?

Dispatchers, interfaces, subsystems and components each solve a different communication problem, and picking the right one for each is most of what keeps an Unreal project clean and fast to grow.

I design decoupled, event-driven architectures in 100% Blueprint.

If your project is a web of direct references, that is exactly what I fix.

Development Hub

Return to Video Game Development to see the full process, from first prototype to finished build.

Consultation

Ready to turn your gameplay concept into a scalable system?

Book a free call or send your project brief

Dev Store

Production-ready Blueprint plugins and system modules.