[UE5] UMG Basics: Building a HUD with Widget Blueprints

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

An illustrated walkthrough of getting an HP bar on screen with UMG, covering the Create Widget → Add to Viewport flow and how anchors work. Includes a hands-on build of a lightweight HUD that updates only when health changes, without relying on Binding.

You can build game logic just fine, but the moment you get to UI, everything stalls. In UMG the usual walls are "I made a Widget but nothing appears" and "I changed the resolution and everything shifted." Both take seconds to fix once you know the cause, and block you completely until you do.

This article breaks getting an HP bar on screen into two stages, make the Widget and put it on screen, then explains how anchors work and how to build a lightweight HP bar that only updates when you take damage.

Illustration of a Widget Blueprint as a single sheet laid over the game screen

What You'll Learn

  • UI doesn't appear just because you made it. It takes Create Widget plus Add to Viewport
  • The split of responsibilities: Designer tab for looks, Graph tab for behavior
  • Resolution breakage comes from anchors, the setting for which part of the screen you measure from
  • Binding is easy but runs constantly. An Event Dispatcher runs only on change
  • Hands-on: an HP bar that updates only when you take damage

Sponsored

Widgets Take Two Steps: Make and Place

This is the first wall. Creating a Widget Blueprint puts nothing on screen.

Three-stage diagram: a Widget Blueprint as a design, Create Widget producing an instance, and Add to Viewport placing it on screen

A Widget Blueprint is a design. From that design, Create Widget produces an instance, and Add to Viewport places it on screen. Only then is it visible.

Event BeginPlay (Player Controller)
  → Create Widget (Class: WBP_PlayerHUD, Owning Player: Self)
  → Set HUDRef (keep the created instance in a variable)
  → Add to Viewport (Target: HUDRef)

The trick is to save the instance in a variable. Later, when you want to update the HP bar or close a menu, it's awkward to operate on it without a reference.

To remove it, use Remove from Parent. It's the inverse of Add to Viewport, taking the widget off the screen.

Add to Viewport is only for widgets you display on their own. Child widgets you placed inside a parent in the Designer show up automatically when the parent goes on screen, so you don't call it individually.

Where to write it: for UI that's on screen for the whole game, like a HUD, the Player Controller's BeginPlay is the standard place. Put it on the character and the UI gets rebuilt every time the character dies and respawns. For the bigger picture of how these roles divide up, see GameMode / GameState / PlayerState.

The Designer Tab and the Graph Tab

Open a Widget Blueprint and you'll see a Designer / Graph toggle in the top right. Their roles are cleanly separated.

Diagram placing the layout-building Designer tab on the left next to the node-building Graph tab on the right
TabWhat it's forWhat you touch
DesignerBuilding the look: arranging, sizing, coloringPalette (available parts), Hierarchy (parent/child), Details
GraphBuilding the behavior: what happens on press, when to updateThe same node graph as any Blueprint

The parts you place in the Designer come in roughly two kinds.

  • Display parts: Text, Image, Progress Bar, Button, and anything else actually drawn on screen
  • Layout parts (containers): Canvas Panel, Horizontal Box, Vertical Box, and others that decide how their contents are arranged

Beginners struggle with the second kind. In particular, without knowing that Canvas Panel allows free positioning while Horizontal Box / Vertical Box auto-arrange their contents, you'll run into "I can't move this where I want."

A reliable default: make the root a Canvas Panel, put Horizontal Box or Vertical Box inside it, and put your parts inside those. Free positioning for the broad layout, auto-arrangement for the fine details holds up well.

Anchors: Which Part of the Screen You Measure From

"It looked perfect on my monitor, then I changed resolution and everything moved." That's the single most common UMG accident, and it comes from not understanding anchors.

Diagram comparing how top-left, center, and full-screen-stretch anchors behave when the screen size changes

An anchor specifies which part of the parent Canvas Panel a part measures its position from. The root Canvas Panel fills the screen, so in practice it becomes "which part of the screen you measure from."

You can only set it on direct children of a Canvas Panel. Select a part inside a Horizontal Box and Anchors won't appear, so remember that the thing you anchor is the container placed directly on the Canvas Panel.

AnchorMeasured fromUI it suits
Top-leftDistance from the top-left of the screenStatus displays like health and stamina
Top-right / bottom-rightDistance from that cornerMinimaps, clocks, ammo counters
CenterOffset from the screen centerCrosshairs, damage numbers
Stretch to fillFollows all four edges (the size scales)Background dimmers, full-screen menus

The question to ask is "when the screen gets wider, where should this part be?" If it should stay in the top-left, anchor it top-left. If it should always be centered, anchor it center.

If it's slightly off-center when you wanted it centered, check Alignment. The anchor decides the reference point; Alignment decides which part of the widget lines up with that point. For exact centering, set Alignment to (0.5, 0.5).

There's a way to check your work, too. Use the resolution dropdown at the top of the Designer tab (Screen Size / Preview Size) to switch to a different device or aspect ratio, and broken parts show up immediately. You catch them before ever testing on hardware.

Sponsored

Two Ways to Get Values into the UI

You want the HP bar to track health. There are two approaches, and which you pick changes the performance cost.

Comparison diagram: Binding asking for the value constantly while visible, versus an Event Dispatcher announcing only when the value changes

Approach 1: Binding

Create a function from the Bind button next to a Progress Bar's Percent, and UMG calls that function every Tick for as long as the widget is alive to fetch the value.

  • Upside: a few clicks, and you can never forget to update
  • Watch out: it keeps getting called the entire time the UI is visible. Put a Cast or a search in there and it runs endlessly
  • Delete it when you switch: if you later move to updating with Set Percent, delete the Binding first. Leave it in place and the value you set gets overwritten by the Binding's return value

Approach 2: Event Dispatcher (announce only on change)

The side that loses health broadcasts "I took damage," and the UI updates only when it hears that.

  • Upside: it only runs when something changed. For a value that changes once in a hundred frames, that's one hundredth of the cost
  • Watch out: setting up the broadcast takes a bit of work (→ how to use Event Dispatchers)

Besides these two, there's also calling Set Percent directly from outside (with something like the Player Controller responsible for updates). Here we're comparing the two most common options.

The deciding factor is how often the value changes. Things that move every frame (a speedometer, health in a game where it's constantly fluctuating) suit Binding; things that change occasionally (HP, score, money, ammo) suit an Event Dispatcher. When in doubt, build it with Binding first and move it once it gets heavy is the practical answer.


Hands-On: An HP Bar That Updates Only on Damage

An action RPG's health gauge, a shooter's shield meter, a survival game's hunger bar. "Show a number as a bar" shows up in every genre. Here we'll build an HP bar that updates only at the moment you take damage.

HP isn't a value that changes constantly; it moves only at the moment you take damage. That fits the guideline from the previous section (rarely-changing values suit an Event Dispatcher), so this article skips Binding and builds it with an Event Dispatcher from the start.

Here's what it looks like when it runs. The HP bar in the top-left stays full until damage lands. Only the instant a hit connects does the bar slide down to match the current value.

Comparison figure: before the hit the HP bar is full at 100, after the hit the bar has dropped to 75

What you'll need: prepare the following.

LocationNameType / setting
BP_Player (inherits Character)MaxHealthFloat / 100.0
BP_PlayerCurrentHealthFloat / 100.0
BP_PlayerOnHealthChangedEvent Dispatcher. Add one input, NewPercent (Float)
WBP_PlayerHUDHealthBarProgress Bar. Check Is Variable
BP_PlayerController (inherits PlayerController)HUDRefA variable of type WBP_PlayerHUD

Create the Widget Blueprint by right-clicking in the Content Browser → User InterfaceWidget Blueprint → choose User Widget as the parent class.

Do this first: creating BP_PlayerController doesn't mean it gets used. Open your GameMode (BP_GameMode or similar) and set Player Controller Class to BP_PlayerController and Default Pawn Class to BP_Player. Skip this and BeginPlay never even runs, leaving you stuck on "nothing appears" indefinitely.

Step 1: Build the HUD layout. In WBP_PlayerHUD's Designer tab, place a Horizontal Box inside the Canvas Panel, and inside it put a Text (reading "HP") and a Progress Bar (HealthBar).

Two settings matter. Select the Horizontal Box and anchor it top-left, then position it in the screen's top-left corner. And set HealthBar's Slot Size to Fill (the default Auto makes the bar almost invisibly narrow).

Step 2: Put the HUD on screen. Create and place it in BP_PlayerController's BeginPlay.

Node graph wiring BeginPlay into Create Widget, Set HUDRef, and Add to Viewport
Event BeginPlay (BP_PlayerController)
  → Create Widget (Class: WBP_PlayerHUD, Owning Player: Self)
  → Set HUDRef
  → Add to Viewport (Target: HUDRef)

Press Play once here. If "HP" and a full bar appear in the top-left, the first half works. If nothing appears, check the GameMode settings or Add to Viewport; if the bar is too thin, check Size = Fill.

Step 3: Broadcast when health drops. Add a function to BP_Player that reduces health and fires OnHealthChanged the moment it changes.

Name the function ReduceHealth. ApplyDamage collides with a built-in engine node of the same name and makes node searches confusing. To evolve this into enemy attacks that drain health through Apply DamageEvent Any Damage, head to health and damage with Apply Damage.

Node graph where ReduceHealth subtracts from CurrentHealth, computes the ratio, and Calls OnHealthChanged
Function: ReduceHealth (input: DamageAmount / Float)
  → Set CurrentHealth
      = Clamp (Value: Subtract (A: CurrentHealth, B: DamageAmount), Min: 0.0, Max: MaxHealth)
  → Call OnHealthChanged (NewPercent: Divide (A: CurrentHealth, B: MaxHealth))

Percent is a ratio from 0.0 to 1.0, so pass CurrentHealth ÷ MaxHealth. Pass the raw value and the bar sits pinned at full with 100 HP and never moves.

Add a test hook while you're here. In BP_Player's event graph, calling ReduceHealth (DamageAmount: 25.0) from any key input is enough. That lets you verify everything before building an enemy.

Step 4: Receive it on the UI side. Add a custom event in WBP_PlayerHUD that listens for the broadcast.

Node graph from a custom event bound to OnHealthChanged into Set Percent
WBP_PlayerHUD's Event Construct
  → Get Owning Player Pawn
  → Cast To BP_Player
      Success → Bind Event to OnHealthChanged (Target: the Cast's As BP Player)
                Event pin ← Custom Event: UpdateHealthBar (input: NewPercent / Float)
          → Set Percent (Target: HealthBar,
                         In Percent: Divide (A: As BP Player's CurrentHealth, B: MaxHealth))  ← ★ syncing the initial value

UpdateHealthBar
  → Set Percent (Target: HealthBar, In Percent: NewPercent)

The ★ line is the important one. An Event Dispatcher won't tell you about changes that happened before you bound to it. Without reading the current value yourself right after binding, you end up with a UI that joined late and sits at full health.

Don't forget to wire the Cast result (As BP Player) into Bind Event to OnHealthChanged's Target. Choosing Add Custom Event for Dispatcher from the Event pin creates an event with matching parameter types automatically.

Press Play and hit your test key. 75% on the first press, 50% on the second, empty after four means it works. Note that UpdateHealthBar is never called except at the moment health drops. That's the whole point of building it this way.

If something's off, work from the symptom.

  • The bar doesn't moveBind Event's Target is empty, or it isn't wired to Event Construct
  • Cast To BP_Player fails → the GameMode's Default Pawn Class is wrong, or possession hasn't happened yet. Moving the widget creation later in the Player Controller's BeginPlay makes it stable
  • The bar starts empty or stays full → the ★ initial value sync is missing

Two things to take away.

  • Let the UI handle display only: BP_Player calculates health, WBP_PlayerHUD displays it. Decide up front that no game logic runs inside the UI, and rebuilding the UI never breaks your rules
  • Always read the current value once, right after binding: a Dispatcher doesn't report past changes. You need something that tells whoever joined mid-way what the situation is now

Once it works, deliberately break it once to cement the idea. Add a Bind to HealthBar's Percent with a function that always returns 1.0, and press Play. No matter how many times you call ReduceHealth, the bar snaps back to full, because the Binding overwrites the value you set with Set Percent every frame. This is exactly why the earlier section said to delete the Binding when you switch. Delete the Bind again once you've seen it.

To animate the gauge draining smoothly, either push the value you pass into Set Percent toward the target with FInterp To, or use UMG's Animation feature. Once you have enough UI that it starts feeling heavy, move on to UMG performance optimization.

Sponsored

Bonus: Good to Know for Later

You can put the game world itself on the UI. An Image's Brush takes a texture — and that texture can be a live camera feed (a Render Target). Minimaps and character-select previews are built this way (→ Minimaps with Render Targets).

"The mouse cursor doesn't appear when I open a menu" is a missing setting. You need two things on the Player Controller: Set Show Mouse Cursor (true) and Set Input Mode UI Only (or Game and UI). When closing, revert both. Set Input Mode Game Only alone leaves the cursor visible, so remember to call Set Show Mouse Cursor (false) too.

Event Construct isn't necessarily once. Remove a widget from the screen and add it back, and it fires again each time. For work that should only initialize once, Event On Initialized is safer.

If you're building the same part repeatedly, turn the widget into a component. For things like inventory item slots, where you line up identical visuals, make a dedicated Widget Blueprint (say, WBP_ItemSlot) and place it inside the parent widget. It's the same idea as componentizing a Blueprint, and fixes only need to happen in one place.

When Text Block text gets cut off, start with the width. The cause is one of: Auto Wrap Text disabled (UMG Text doesn't wrap by default), the parent Slot being too narrow, the Clipping setting, or the font size being too large. For long passages, use Auto Wrap Text; to keep it on one line, adjust the parent's width or use a Size Box.

What this looks like in C++. The standard approach is to build the UI itself in Widget Blueprints and keep the C++ side to passing values in and receiving events. Create a class inheriting UUserWidget and use BlueprintImplementableEvent to call "please update," and visual tweaks stay entirely on the designer's side.

// Written inside a Player Controller (this becomes the Owning Player)
if (UUserWidget* HUD = CreateWidget<UUserWidget>(this, HUDClass))
{
    HUD->AddToViewport();
}

This does exactly what Blueprint's Create Widget plus Add to Viewport does. The first argument is the Owning Player, so inside a Player Controller you pass this (passing GetWorld() changes the ownership relationship).


Summary

UMG problems mostly boil down to these three.

SymptomCauseFix
Nothing on screenYou made it but never placed itCreate Widget plus Add to Viewport
Breaks at other resolutionsNo anchors setDecide by "where should this be when the screen gets wider?"
HeavyToo much logic stuffed into a BindingMove rarely-changing values to an Event Dispatcher

And there's one design principle. Keep the UI focused on display. Holding values and calculating them is the game logic's job. Hold that line and rebuilding the UI never breaks the game.

That HUD you're building right now: is it calculating any game values inside itself?