Skip to content

Updating a Widget: Bindings vs Events

A health bar does not need to ask "what is my health?" every frame. Most UI bugs get easier when the gameplay object owns the value and tells the widget when that value changes.

The one-minute version

  • A UMG property binding is a getter attached to a widget property. While the widget is active, Unreal can call that getter repeatedly to ask for the current value.
  • Treat bound functions as per-frame UI code. They must be cheap, pure, and side-effect free. Do not cast, search the world, spawn, save, print spam, or change gameplay state inside a binding.
  • The beginner-safe pattern is event-driven: gameplay changes health once, calls an Event Dispatcher once, and the widget updates its progress bar once.
  • A dispatcher does not replay old values. After binding the widget, do one initial manual refresh so the bar is correct before the next health change.
  • Store the widget reference that came from Create Widget. If you create a new widget every time, you also create new bindings, new state, and new confusion.
  • Bindings are not evil. They are fine for tiny presentational values while learning. They become a problem when they hide gameplay work in a function the UI may call constantly.

What a binding actually does

In the Widget Blueprint designer, many properties have a Bind dropdown: text, visibility, enabled state, progress bar percent, color, and so on. When you create a binding, UMG creates a function that returns the value that property should use.

For a progress bar:

ProgressBar.Percent is bound to function GetHealthPercent

function GetHealthPercent():
    return CurrentHealth / MaxHealth

The part beginners miss is timing. The binding is not a one-time assignment. It is a question the UI can keep asking while it lays out, paints, invalidates, or otherwise refreshes the widget. At beginner altitude, the practical rule is: assume a bound function can run every frame while the widget is visible.

That means this binding is acceptable:

function GetHealthPercent():
    if MaxHealth <= 0:
        return 0

    return CurrentHealth / MaxHealth

This one is a trap:

function GetHealthPercent():
    Get Player Character
    Cast To BP_PlayerCharacter
    Get All Actors Of Class BP_SaveManager
    Print String "Updating health"
    return Player.Health / Player.MaxHealth

It may look harmless because it is hidden behind a pure return pin. It is still work, and it still runs on the game thread when the UI asks for the value.

The event-driven alternative

Most HUD values change because gameplay did something specific:

  • damage arrived,
  • ammo was spent,
  • an item was picked up,
  • the score changed,
  • the player entered a trigger,
  • the objective state changed.

When a value changes at a known moment, push that change to the widget at that moment instead of making the widget poll for it.

For health:

BP_PlayerCharacter:
    Variables:
        Health
        MaxHealth

    Event Dispatcher:
        OnHealthChanged(CurrentHealth, MaxHealth)

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

Then the player UI setup path creates and connects the widget:

PlayerController BeginPlay
-> Create Widget WBP_HealthBar
-> Store HealthBarWidget
-> Get Controlled Pawn
-> Cast To BP_PlayerCharacter
-> Bind Event to OnHealthChanged
      HealthBarWidget.UpdateHealth(CurrentHealth, MaxHealth)
-> HealthBarWidget.UpdateHealth(Character.Health, Character.MaxHealth)
-> Add to Viewport

Inside WBP_HealthBar:

function UpdateHealth(CurrentHealth, MaxHealth):
    if MaxHealth <= 0:
        ProgressBar.SetPercent(0)
    else:
        ProgressBar.SetPercent(CurrentHealth / MaxHealth)

That shape has two important properties:

  1. The character owns health, so it owns the fact that health changed.
  2. The widget owns visuals, so it owns converting health into a progress bar percent.

The character does not need to cast to a specific widget class every time it takes damage. The widget does not need to cast to the character every frame.

Why the initial refresh matters

Dispatchers tell listeners about future calls. They do not store the latest event and replay it to late listeners.

This graph has a common bug:

Create Widget WBP_HealthBar
-> Bind Event to Character.OnHealthChanged
-> Add to Viewport

If the character already took damage before the widget bound, the widget will not automatically hear about that old health change. It waits for the next dispatcher call.

Fix it by binding, then calling the widget's update function once with the current value:

Bind Event to Character.OnHealthChanged
-> HealthBarWidget.UpdateHealth(Character.Health, Character.MaxHealth)

This is not duplicate work. It is lifecycle setup: "listen for future changes, and also show the current state right now."

When bindings fail (and what failure looks like)

Bindings usually fail by showing stale or wrong UI, not by stopping execution.

Common causes:

  1. The binding reads a None reference. The widget tried to read the player, inventory, or manager before that reference was assigned. Debug it like any other Accessed None problem.
  2. The binding does expensive work. A slow cast/search/log inside a binding becomes repeated work. At 60 frames per second, tiny mistakes compound.
  3. The binding has side effects. A getter that changes health, creates widgets, starts timers, or calls dispatchers can run more often than you expected and create loops.
  4. You set the property directly and expect the binding to stay in charge. Epic's UMG binding docs call out that directly setting a bound widget property can break that binding. Pick one owner for the value.
  5. The widget was re-created. The new widget starts with defaults and has only the bindings/setup you run for that new instance.
  6. The math returns the wrong range. A progress bar percent is 0.0 to 1.0, not 0 to 100. Clamp or divide before setting it.

Bindings are easiest to debug by putting a breakpoint inside the binding function and watching how often it runs. If the function runs constantly, move real work out of it.

When event-driven updates fail

The event-driven version has different failure shapes:

  1. The bind path never ran. The dispatcher call works, but nobody is listening.
  2. You bound to the wrong instance. The widget listens to an old character while the player is controlling a respawned one.
  3. You called before binding. The value changed before the widget existed, so you need the initial refresh.
  4. The widget reference is None. Creation failed, or you did not store the return value from Create Widget.
  5. The widget was removed and re-created. The old binding belonged to the old object. The new widget needs setup too.
  6. The dispatcher runs on the wrong machine. In multiplayer, UI is local. Use replication or client-side notifications to get the state to the machine that owns the screen, then update that local widget.

Debug with two breakpoints: one on the Bind Event node and one where health changes and calls the dispatcher. If both fire on the same character instance, the widget should receive the update.

What neither approach does for you

  • A binding does not decide where the real gameplay value should live.
  • An Event Dispatcher does not find the widget; the widget/setup path must bind to the broadcaster.
  • A widget should not be the source of truth for health, ammo, score, or saved progress. It displays state owned elsewhere.
  • Updating UI does not replicate gameplay state.
  • Removing a widget from the viewport does not automatically unbind every relationship you created. If the widget can outlive or switch broadcasters, unbind deliberately during teardown or rebinding.

Which one should I use?

Tool Use when Avoid when
Direct Set node on the widget You already have the widget reference and one event has new data. The caller has to know too much about many child widgets. Prefer a widget function.
Widget function, like UpdateHealth The widget should translate gameplay data into its own visuals. You are calling it every Tick just to poll unchanged values.
Event Dispatcher Gameplay state changes and one or more listeners should react. You need an immediate answer from one known target.
UMG property binding The value is tiny, pure, and cheap to recompute while learning or prototyping. The binding casts, searches, mutates state, logs, or depends on fragile timing.
Widget Tick The widget has genuine per-frame presentation work, such as an animation not handled by UMG animation tools. You are polling gameplay state that could be pushed by an event.
MVVM / Viewmodel A larger UI needs a formal data model and field notifications. A first health bar only needs a stored widget reference and one update function.

Rule of thumb: state changes by event, UI paints from cached values. Use bindings only when the bound function is so small you would not care if it ran every frame.

Going deeper