Skip to content

Event Dispatchers

At a glance

Lives in: Blueprint API / Blueprint Communication - Returns: nothing; dispatchers can pass inputs to bound events, but the call has no output values - Fails by: doing nothing when no events are bound, while execution continues - Main inputs: the dispatcher owner, the event list, and any dispatcher parameters - Official docs: Event Dispatchers, Binding and Unbinding Events, Creating Dispatcher Events, and Calling Event Dispatchers

The one-minute version

  • An Event Dispatcher is a broadcast list owned by one object. Other events can bind to that list, and the owner can call the dispatcher later.
  • The golden rule is direction: the broadcaster owns the dispatcher; listeners bind to the broadcaster.
  • Call means "fire every event currently bound to this dispatcher." If nothing is bound, nothing happens.
  • Bind Event adds a matching event to the list. Assign is the editor shortcut that creates a matching event and a bind node for you.
  • Unbind removes one bound event. Unbind All clears the list for the target scope, which can be wider than beginners expect.
  • Dispatchers do not find listeners, remember past calls, return values, or replicate by themselves.

What it actually does

An Event Dispatcher is Unreal's Blueprint-facing version of a multicast delegate: a named event list with a fixed signature. The signature is the inputs you define on the dispatcher.

For a health example:

BP_PlayerCharacter owns:
    Event Dispatcher OnHealthChanged
        Inputs:
            CurrentHealth (float)
            MaxHealth (float)

WBP_HealthBar binds:
    OnHealthChanged -> UpdateProgressBar

BP_PlayerCharacter calls:
    OnHealthChanged(CurrentHealth, MaxHealth)

At runtime, the call is closer to this:

pseudocode of the engine behavior - not engine source

function CallOnHealthChanged(Character, CurrentHealth, MaxHealth):
    for each bound event in Character.OnHealthChanged:
        run bound event with CurrentHealth and MaxHealth

    return nothing

The character does not know whether the listener is a widget, sound manager, door, quest tracker, or nothing at all. It only owns the dispatcher and decides when to broadcast.

That is the point of the tool. A dispatcher lets the object with the fact say "this happened" without hard-coding every object that might care.

The owner/listener rule

Put the dispatcher on the object whose state changes.

Event Dispatcher belongs on Listener examples
Health changed Character, enemy, or health component Health bar, audio, damage vignette
Door finished opening Door actor Quest trigger, sound actor, checkpoint
Boss died Boss actor or boss encounter controller Door, reward spawner, music manager
Inventory changed Inventory component or controller-owned inventory object HUD, save manager, tutorial prompt

Do not put OnHealthChanged on the widget just because the widget is the thing you want to update. The widget is a listener. The character or health component is the broadcaster because it owns the health state.

flowchart LR
    Damage[Apply damage] --> Character[BP_PlayerCharacter]
    Character --> Call[Call OnHealthChanged]
    Call --> Widget[WBP_HealthBar bound event]
    Call --> Audio[Low-health audio bound event]
    Call --> Quest[Quest tracker bound event]

This direction keeps the character independent. It can keep working in a test map with no HUD, and the HUD can be recreated without rewriting damage logic.

Bind, Assign, Call, Unbind

These nodes all operate on the dispatcher's event list.

Node What it does Beginner trap
Bind Event to X Adds a compatible event to the dispatcher's list. The event does not run now; it runs later when Call X happens.
Assign X Creates a matching event node and a bind node already wired together. It is not a special "replace every listener" command. Treat it as a convenience bind.
Call X Runs the events currently bound to the dispatcher. If the list is empty, nothing visible happens.
Unbind Event from X Removes that matching event from the list. The event signature and target must match the bound event you mean to remove.
Unbind All Events from X Clears every event bound to that dispatcher for the target scope. It can remove other listeners, including Level Blueprint bindings, so use it deliberately.

Bind Event and Assign both need a target: the object whose dispatcher list you are changing. Inside the broadcaster Blueprint, that target is usually Self. In another Blueprint, the target must be a real reference to the broadcaster instance.

Create Widget WBP_HealthBar
-> Get Player Character
-> Cast To BP_PlayerCharacter
-> Bind Event to OnHealthChanged

The event you bind must match the dispatcher's signature. If the dispatcher has CurrentHealth and MaxHealth inputs, the bound event receives those same values.

Where to bind and when

Bind once, at the moment both sides exist.

Common places:

Listener Bind when Usually unbind when
Level Blueprint BeginPlay, after getting a placed actor reference. Rarely, unless changing targets.
Actor listening to another actor BeginPlay, after assignment, or right after spawn. EndPlay or before switching broadcaster targets.
Widget listening to a character After the widget is created and it has the character reference. Destruct, Remove from Parent cleanup, or before rebinding to a new character.
Spawn manager listening to spawned actor Immediately after Spawn Actor from Class returns a valid actor. When the spawned actor is no longer relevant.

Do not bind from Tick. Do not bind every time health changes. Do not bind inside a function that might run repeatedly unless the function first proves the binding is not already established.

One more lifecycle rule: dispatchers do not replay history. If the widget binds after the character already lost health, the widget will not automatically receive the old OnHealthChanged call. Bind, then do an initial manual refresh:

Bind Event to OnHealthChanged
-> UpdateHealthBar(CurrentHealth, MaxHealth)

When it fails (and what failure does)

There is no Cast Failed pin on a dispatcher call. The usual failure is silence.

Common causes:

  1. No events are bound. Calling a dispatcher with an empty list has no effect.
  2. You bound on the wrong instance. Binding to Enemy_1.OnDied does not listen to Enemy_2.OnDied.
  3. You never executed the bind path. A dispatcher event node sitting in the graph is not enough. It has to be connected through Bind Event, Assign, or a special auto-bound Level Blueprint event.
  4. The broadcaster reference is None. Binding or calling through an empty object reference is still an object-reference problem; debug it like Accessed None.
  5. The listener went away or the target changed. Recreated widgets, respawned pawns, and replaced actors need fresh bindings to the current broadcaster.
  6. You expected a return value. A multicast dispatcher tells listeners something happened. It does not ask them for answers.
  7. You expected a network message. Dispatcher calls are local Blueprint execution. Use the normal server/client remote procedure call path when multiplayer authority matters.

When debugging, put a breakpoint on the bind node and the call node. If the bind never runs, the call has nobody to notify. If the call never runs, the listener cannot fire.

Why it fired more than once

When a dispatcher seems to fire N times, first count the bindings.

Important nuance: Epic's docs state that the same event can be bound only once to a dispatcher. Running the same Bind Event node again for the same event is not usually the duplicate-binds bug beginners imagine.

The real repeat cases are usually:

  • several different listener events are bound to the same dispatcher,
  • several listener instances are alive, such as old widgets you never removed,
  • each respawn created a new character, widget, or manager and you are listening to more than one of them,
  • you bound both in a Blueprint Class and in the Level Blueprint,
  • you call the dispatcher from more than one gameplay path.

For the respawn trap, print the broadcaster and listener names when binding:

Print String "Bind " + ToString(self) + " to " + ToString(Character)

If you see a new widget binding every death while the old widget still exists, the fix is lifecycle cleanup, not a different dispatcher node.

What Event Dispatchers do not do

  • They do not find listeners. Listeners need a reference to the broadcaster and must bind.
  • They do not store current state or replay missed events.
  • They do not return values from listeners.
  • They do not guarantee listener execution order. Do not build gameplay rules that depend on which listener runs first.
  • They do not automatically mean "server to client" or "client to server."
  • They do not replace interfaces for one target command calls.
  • They do not make hard references disappear if your dispatcher signature uses concrete Blueprint classes as parameter types.

The pattern everyone actually uses

For a health bar:

BP_PlayerCharacter:
    Variable Health
    Variable MaxHealth
    Event Dispatcher OnHealthChanged(CurrentHealth, MaxHealth)

    Function ApplyHealthDelta(Delta):
        Health = Clamp(Health + Delta, 0, MaxHealth)
        Call OnHealthChanged(Health, MaxHealth)

PlayerController or HUD setup:
    Create Widget WBP_HealthBar
    Get controlled character
    Cast To BP_PlayerCharacter
    Bind Event to OnHealthChanged
        -> WBP_HealthBar.UpdateHealth(CurrentHealth, MaxHealth)
    WBP_HealthBar.UpdateHealth(Health, MaxHealth)
    Add to Viewport

For a spawned boss:

Spawn Actor from Class (BP_Boss)
-> Is Valid Return Value
-> Bind Event to OnDied on that returned boss reference
    -> OpenExitDoor
    -> SpawnReward

Bind right after spawn because that is the moment you have the specific boss instance. Calling OnDied on one boss does not notify bindings on another boss.

The C++ twin, for the curious (our own example code):

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
    FOnHealthChanged,
    float, CurrentHealth,
    float, MaxHealth);

UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;

void ATrainingCharacter::ApplyHealthDelta(float Delta)
{
    Health = FMath::Clamp(Health + Delta, 0.0f, MaxHealth);
    OnHealthChanged.Broadcast(Health, MaxHealth);
}

Lookalikes - which one do I want?

Tool Question it answers Use when
Event Dispatcher "Whoever is listening, this happened." One broadcaster has zero, one, or many listeners.
Custom Event "Start this named flow." Something calls it directly, or a timer/delegate binding uses it as an entry point.
Blueprint Interface "Can this target answer this contract?" You have a target object and several possible classes should respond.
Cast To X "Is this object this specific class family?" You need members from one known class.
Direct function call "Tell this known object to do this." You already hold a typed reference to the exact target.
Timer by Event "Run this event later or repeatedly." Time scheduling is the point; it is not a listener list by itself.

Rule of thumb: use a dispatcher when the broadcaster should not know its listeners. Use an interface when the caller has one target and knows the contract. Use a cast when the caller is truly coupled to one class.

Going deeper

  • Blueprint Interfaces - the contract-based call when you have one target object.
  • Cast To X - the class-specific option.
  • The Blueprint survival kit - functions, custom events, and macros before cross-Blueprint communication.
  • Breakpoints and watch values - how to prove whether the bind path and call path both executed.
  • Spawn Actor from Class - where spawned actor references come from before binding to their dispatchers.
  • Official docs: Blueprint Communication Usage, Event Dispatchers, Binding and Unbinding Events, Creating Dispatcher Events, Calling Event Dispatchers, and TMulticastDelegate.
  • Engine source (requires engine access - we do not reproduce it here): Blueprint delegate nodes such as UK2Node_AddDelegate, UK2Node_AssignDelegate, UK2Node_RemoveDelegate, UK2Node_ClearDelegate, and UK2Node_CallDelegate live under Engine/Source/Editor/BlueprintGraph/Classes/ with shared expansion logic in Engine/Source/Editor/BlueprintGraph/Private/K2Node_MCDelegate.cpp; multicast delegate property metadata lives in FMulticastDelegateProperty at Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h; script multicast delegate storage and invocation helpers live in Engine/Source/Runtime/CoreUObject/Public/UObject/ScriptDelegates.h.