Skip to content

Buttons, Progress Bars, and Basic UMG Events

A button click should usually call a UI or gameplay function. A progress bar should display a value that gameplay already owns. The clean split is what keeps a first HUD from turning into world-search glue.

The one-minute version

  • A Button is a clickable widget. Its common events are OnClicked, OnPressed, OnReleased, OnHovered, and OnUnhovered.
  • OnClicked is the normal "activate this button" event. OnPressed and OnReleased are lower-level press-state events.
  • A Progress Bar uses Percent in the 0.0 to 1.0 range, not 0 to 100.
  • A Text Block displays Text. Calling SetText directly can replace the displayed text and break an existing text binding.
  • Widget events should call named functions or dispatchers, not blindly search the world from inside the widget.
  • Gameplay owns gameplay state; widgets own presentation.

Button events

A UMG Button wraps one child widget and exposes interaction events.

Event Beginner meaning Typical use
OnClicked The button was activated as a click. Resume, Start Game, Buy, Equip, Confirm.
OnPressed The pointer/key/gamepad button went down on the button. Hold-to-charge UI, pressed visual/sound.
OnReleased The press ended. Stop hold behavior or release visual.
OnHovered / OnUnhovered Pointer entered/left the button. Hover sounds, descriptions, highlight state.

Use OnClicked first unless you specifically need press duration.

ResumeButton.OnClicked
-> Call OwningPlayerController.ClosePauseMenu

That is better than:

ResumeButton.OnClicked
-> Get All Actors Of Class BP_PlayerController
-> Get index 0
-> Cast
-> ClosePauseMenu

The widget should already know its owning player or receive a reference during setup.

Progress bars

Progress bars display a normalized value:

Percent = CurrentHealth / MaxHealth

That means:

CurrentHealth = 75
MaxHealth = 100
Percent = 0.75

It does not mean 75. If you set a progress bar percent to 75, you are far outside the intended UI range. Clamp your result:

if MaxHealth <= 0:
    Percent = 0
else:
    Percent = Clamp(CurrentHealth / MaxHealth, 0, 1)

Put that conversion inside a widget function:

WBP_HealthBar.UpdateHealth(CurrentHealth, MaxHealth)
-> Set Percent on HealthProgressBar
-> Set Text on HealthText

Now the caller does not need to know your widget's child names.

Text updates

Use Text values for player-facing text. Convert numbers to text at the UI boundary:

UpdateAmmo(CurrentAmmo, MaxAmmo)
-> Format Text "{Current}/{Max}"
-> AmmoText.SetText

Be aware of bindings. A direct SetText call on a bound Text property can wipe the binding. Pick one owner:

  • binding function owns the value, or
  • event/function call sets the value.

For this wiki's beginner patterns, event-driven updates are usually clearer than property bindings for gameplay HUD values.

Where logic belongs

Think of the widget as the view:

Gameplay object:
    owns health, inventory, ability cooldown, score

Widget:
    owns progress bars, labels, buttons, layout

Controller/HUD setup:
    creates widget and connects the two

For a button that buys an item:

ShopWidget BuyButton.OnClicked
-> ShopWidget calls RequestBuySelectedItem dispatcher
-> PlayerController or ShopComponent handles the request
-> gameplay validates currency/inventory
-> gameplay broadcasts updated values
-> widget refreshes display

The button does not directly subtract money from a random actor it found in the level. It asks the owner that was wired during setup.

When UI events fail

Symptom Likely cause Fix shape
Button click never fires Widget not focusable/visible/enabled, input mode wrong, or another widget blocks hit testing. Check input mode, cursor, visibility, enabled state, and Z-order.
Button fires twice You bound the same event more than once or created multiple widget copies. Store one widget reference and bind once during setup.
Progress bar looks full/empty incorrectly Percent is 0..100, divide by zero, or stale value. Normalize to 0..1, guard Max <= 0, push current values after binding.
Text reverts or binding disappears Direct set fights a binding. Use either binding or direct event updates, not both.
Widget cannot find player data The widget was never given a valid owner/reference. Use Owning Player, controller/HUD setup, dispatchers, or explicit init functions.

Most UI bugs are reference or ownership bugs. Debug setup first: prove which widget instance exists and which gameplay object it is connected to.

What these widgets do not do

  • A button does not validate gameplay rules by itself.
  • A progress bar does not know what "health" means.
  • SetPercent does not clamp your gameplay value for you in a way you should rely on; feed it a sane 0..1 value.
  • SetText does not localize or format complex UI by itself.
  • Widget events do not replicate. UI exists locally; replicated gameplay state must arrive separately.
  • UMG events do not replace input mode, cursor, focus, or pause setup.

The pattern everyone actually uses

For a health bar:

PlayerController BeginPlay
-> Create Widget WBP_HUD
-> Store HUDWidget
-> Add to Viewport
-> Bind to HealthComponent.OnHealthChanged
-> HUDWidget.UpdateHealth(CurrentHealth, MaxHealth)

WBP_HUD.UpdateHealth(CurrentHealth, MaxHealth)
-> Percent = CurrentHealth / MaxHealth, clamped
-> HealthProgressBar.SetPercent(Percent)
-> HealthText.SetText(Format "{Current}/{Max}")

For a pause menu button:

WBP_PauseMenu.OnInitialized
-> ResumeButton.OnClicked binds to HandleResumeClicked

HandleResumeClicked
-> Call ResumeRequested dispatcher

PlayerController after creating menu
-> Bind ResumeRequested to ClosePauseMenu

That keeps the widget reusable: it exposes "resume requested" instead of knowing every detail of the controller's pause implementation.

Lookalikes - which one do I want?

Tool Use when Watch out
OnClicked A button should perform its normal action. Bind once; avoid duplicate widget instances.
OnPressed / OnReleased Press duration or press visuals matter. Not every press becomes a click.
Progress Bar Percent A normalized value should fill a bar. Use 0.0 to 1.0.
SetText You want to directly update a text widget. It can wipe a binding on that property.
Property Binding A tiny pure value can be recomputed by UI. Avoid casts/searches/side effects in bindings.
Event Dispatcher A widget should request action from its owner, or gameplay should notify UI. Dispatchers need binding and lifecycle cleanup.

Rule of thumb: button events request actions, progress bars display normalized values, and widgets receive references instead of searching for the world.

Going deeper