[UE5] How to Implement Event-Driven Design with Event Dispatchers

Created: 2025-12-12Last updated: 2026-07-19

An illustrated guide to decoupling event sources from handlers with Event Dispatchers. Covers the three-step Blueprint rhythm of create, bind, and call, the crash you get from forgetting Unbind, and multicast delegates in C++.

"When the player picks up an item, update the inventory, notify the UI, and play a sound effect." Write that the obvious way and the item's Blueprint ends up knowing about the inventory, the UI, and the audio system. Then you want to change one thing about the sound, and the file you open is the item Blueprint.

The tool that untangles this is the Event Dispatcher. The item just rings a bell to say "I was picked up," and the listeners decide what to do about it. This article walks through Dispatcher implementation as a three-step rhythm, covers the crash you get from forgetting to Unbind, and finishes with delegates in C++.

A figure ringing a bell while lights come on in three houses, illustrating Event Dispatchers

What You'll Learn

  • What a Dispatcher really is: complete separation of source and handler (the observer pattern)
  • The three-step Blueprint rhythm: create (the Dispatcher) → bind → call
  • The crash caused by forgetting Unbind, and how to prevent it
  • Multicast delegates in C++ (BlueprintAssignable / AddDynamic)
  • Hands-on: build a notification network for item pickups

Sponsored

Why You Need It: Named Call Chains Fall Apart

An Event Dispatcher is UE's headline implementation of a delegate, a mechanism for notifying everyone who cares that an event happened. It's the observer pattern. The division of labor looks like this.

RoleWhat it doesWhat it doesn't know
Source (Caller)Rings the bell (Call)Who is listening
Handler (Listener)Registers with the bell (Bind)What's inside the source
Event DispatcherThe bell that mediates between themWhat's inside either one (which is why it breaks the dependency)
Naming every listener directly tangles the wires, but ringing a bell means the source only has to think about ringing

With named direct calls, every new reaction means modifying the source. With a Dispatcher, a new listener just binds, and the source Blueprint doesn't change by a single character.

Sponsored

Hands-On: Building a Notification Network for Item Pickups

Let's actually build the "pick it up and three things happen" scenario from the intro. Gathering materials in an RPG, opening a chest in a roguelike, grabbing a power-up in a shooter: "several systems react at the moment of acquisition" is the same shape in every genre. The implementation goes in three steps.

The three steps of implementing an Event Dispatcher: create the bell, listeners register, ring it when something happens

Setup to reproduce this: place one BP_Item and one BP_InventoryManager in the level. Give the Manager a variable TargetItem (type BP_Item, with Instance Editable checked) and point it at the item in the level (Instance Editable lets you pick the target per placed instance → Intro to Blueprint variables). Remembering how tedious it is to "point at each one by hand" is what makes the central Dispatcher in the later bonus feel like a relief.

Step 1: Create the Bell (Define the Dispatcher)

In BP_Item, create OnItemPickedUp under "Event Dispatchers" in the My Blueprint panel. To add the Inputs, select the Dispatcher you created and use the + in the Inputs section of the Details panel. Put only the minimum information the listeners need to know. Here, that's a reference to the player who picked it up (Instigator).

Step 2: Listeners Register (Bind)

On the receiving side, say in the BeginPlay of BP_InventoryManager, wire a Bind Event to OnItemPickedUp node from the item reference and connect a Custom Event to the Event pin.

BP_InventoryManager (listener)
Event BeginPlay → Bind Event to OnItemPickedUp (Target: reference to the item)
  Event pin ← Custom Event: HandleItemPickup (receives Instigator)
HandleItemPickup → add to inventory → update UI
Node graph in BP_InventoryManager connecting the event pin of Bind Event to OnItemPickedUp from Event BeginPlay to the Custom Event HandleItemPickup

Binding is done by pulling from the red square event pin on the right of Bind Event to OnItemPickedUp and connecting a Custom Event. This registers "when this bell rings, call this process." The UI widget and the audio handler bind the same way. You can have as many listeners as you like.

Step 3: Ring It When It Happens (Call)

The source, BP_Item, just rings the bell the moment it's picked up.

BP_Item (source)
On Component Begin Overlap → (player check) → Call OnItemPickedUp (Instigator: the overlapping Actor) → Destroy Actor
Node graph in BP_Item connecting On Component Begin Overlap through Cast To BP_Player, Call OnItemPickedUp, and Destroy Actor

All the source does is the single Call OnItemPickedUp node. It rings the bell without knowing a thing about who's listening, then destroys itself. That lightness is the essence of loose coupling.

A player picking up a gem rings a bell and three things respond: a bag, the UI, and a speaker

Hit Play and pick up an item. The inventory, the UI, and the sound effect all react at once.

To confirm what's happening, disconnect the audio handler's Bind. Only the sound stops. Everything else keeps working. Put the Bind back and then bind a brand-new "achievement system" as well. You just added a reaction without touching BP_Item at all. You can add reactions without changing the source. That's the reason to use a Dispatcher.

Two things to take away.

  • Keep the arguments minimal: put only "who" and "what" on the bell. If a listener needs a lot of data, it's cleaner for it to fetch that data through a reference than to bloat the signature
  • Name events after what happened: OnItemPickedUp, OnHealthChanged. Name them after a past fact. Naming them after what should be done (UpdateUI and the like) means the source knows about the handler's business, which is a tightly coupled name
Sponsored

The Most Important Rule: Bind and Unbind Come as a Pair

This is the one Dispatcher rule that can actually take your game down: when a listener disappears, its registration must disappear too.

Forgetting to Unbind means ringing at someone who is gone, so cut the registration with scissors before they vanish

Binding also means the source holds a reference to the listener. If a listener disappears without unbinding (widgets are the usual culprit since they're created and destroyed frequently), a reference to a destroyed object lingers, and the next Call causes an Accessed None error or a crash. For widgets, call Unbind Event from XXX before destruction (around Remove from Parent, or in Destruct). For Actors, call it in EndPlay.

Note: when the pair has matching lifetimes, like an Owner binding to a Dispatcher on its own Component, they're destroyed together so this is effectively a non-issue (see the Blueprint Component article). The danger is binding to something with a different lifetime.

Sponsored

Implementation in C++: Multicast Delegates

In C++, an Event Dispatcher is really a dynamic multicast delegate. Adding BlueprintAssignable exposes it as a Dispatcher that Blueprints can bind to as well.

// Item.h
// Declare the delegate type (two-parameter version; the macro name changes with the parameter count)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
    FOnItemPickedUpSignature, AItem*, PickedUpItem, APlayerCharacter*, Instigator);

UCLASS()
class AItem : public AActor
{
    GENERATED_BODY()

public:
    // Expose it as a Dispatcher that Blueprints can bind to
    UPROPERTY(BlueprintAssignable, Category = "Events")
    FOnItemPickedUpSignature OnItemPickedUp;
};
// Item.cpp — the ringing side
void AItem::OnOverlapBegin(/* ... */)
{
    // Ring the bell (equivalent to Call in Blueprint)
    OnItemPickedUp.Broadcast(this, Cast<APlayerCharacter>(OtherActor));
}
// InventoryManager.cpp — the listening side
void AInventoryManager::BeginPlay()
{
    Super::BeginPlay();
    if (AItem* Item = GetItemReference())
    {
        // Register (equivalent to Bind in Blueprint)
        Item->OnItemPickedUp.AddDynamic(this, &AInventoryManager::HandleItemPickup);
    }
}

void AInventoryManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    if (AItem* Item = GetItemReference())
    {
        // Unregister (equivalent to Unbind). Always pair this with AddDynamic
        Item->OnItemPickedUp.RemoveDynamic(this, &AInventoryManager::HandleItemPickup);
    }
    Super::EndPlay(EndPlayReason);
}

The mapping is straightforward: Call = Broadcast, Bind = AddDynamic, Unbind = RemoveDynamic. The basics of writing C++ in UE are covered in your first step from Blueprint to C++.

Bonus: Good to Know for Later

  • The "I want to Bind but I can't get a reference" problem: having the UI individually bind to items that spawn at runtime is painful. The standard answer is to hang the bell somewhere everyone knows about. Create OnAnyItemPickedUp on the GameState or GameInstance, have items ring it there, and have the UI bind there (see the GameState article)
Items that spawn and vanish come ring the GameState's bell, while long-lived UI elements bind there, because the bell hangs somewhere everyone knows about
  • A Button's OnClicked works the same way: the OnClicked on a UMG Button is an Event Dispatcher the engine provides. You've been using Dispatchers without knowing it
  • Don't overuse them: if every piece of communication is a broadcast, "who called this?" becomes hard to trace. A fixed one-to-one relationship is fine as a direct call. For the full picture of when to use what, see the overview of the three communication methods

Summary

  • An Event Dispatcher is a bell that separates source from handler. The source just rings, the listeners just register
  • The implementation is three steps: create → Bind → Call. In C++: Broadcast / AddDynamic / RemoveDynamic
  • The iron rule is "Bind and Unbind come as a pair." Binding across different lifetimes is where accidents happen
  • Adding and removing reactions leaves the source untouched. That's the practical payoff of loose coupling

The other half of loose coupling, the letter with no addressee, gets a deeper treatment in the Blueprint Interface article.

In your game, which moment should the most systems react to? Defeating an enemy? Leveling up? Hang your first bell right there.