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, andOnUnhovered. OnClickedis the normal "activate this button" event.OnPressedandOnReleasedare lower-level press-state events.- A Progress Bar uses
Percentin the0.0to1.0range, not0to100. - A Text Block displays
Text. CallingSetTextdirectly 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.
SetPercentdoes not clamp your gameplay value for you in a way you should rely on; feed it a sane0..1value.SetTextdoes 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¶
- Create Widget + Add to Viewport - creating and storing the widget instance.
- Remove from Parent and widget lifetime - closing widgets without leaving input state broken.
- Updating a Widget: Bindings vs Events - pushing gameplay changes into UI.
- Event Dispatchers - widget-to-owner requests and gameplay-to-widget notifications.
- Set Input Mode & Show Mouse Cursor - making interactive menus actually receive input.
- Official docs: UButton, UProgressBar, UTextBlock::SetText, UMG UI Designer Quick Start Guide, and UMG Styling.
- Engine source (requires engine access - we do not reproduce it here):
UButtoninEngine/Source/Runtime/UMG/Public/Components/Button.handEngine/Source/Runtime/UMG/Private/Components/Button.cpp;UProgressBarin.../Components/ProgressBar.h/.cpp;UTextBlockin.../Components/TextBlock.h/.cpp.