🖼️ Lesson 9.1: UserWidget & UMG in C++
HUDs, menus, health bars — game UI is built with UMG. Designers lay out widgets visually in a Widget Blueprint; your C++ drives the logic behind them. This lesson wires the two together with the same base-class-plus-subclass pattern you mastered for weapons in Module 6.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create a
UUserWidgetsubclass in C++ and pair it with a Widget Blueprint - Bind C++ pointers to designer-placed widgets with
meta = (BindWidget) - Create a widget at runtime and add it to the viewport
- Use
NativeConstructas the widget's init hook - Add the UMG module dependency
Estimated Time: 60 minutes
Engine Version: Unreal Engine 5.8
In This Lesson
The UMG Split
UMG has two halves that mirror the C++/Blueprint division you already know:
visual layout: text, bars, buttons"] -->|"is a subclass of"| CPP["UMyHUDWidget (C++)
logic: update values, handle clicks"] CPP -->|"BindWidget pointers"| WBP
UUserWidget base holds the logic. BindWidget connects C++ pointers to the widgets the designer placed — the same base/subclass split as Lesson 6.4.📖 Why not build the layout in C++?
You can construct widgets entirely in code (via Slate), but it's verbose and non-visual. The idiomatic approach is: lay out visually in a Widget Blueprint, drive with C++. Designers arrange and style; you bind to the pieces you need and control behavior. It's the hybrid model (Lesson 1.1) applied to UI.
Module & Base Class
UMG lives in its own module — add it to .Build.cs (Lesson 2.1/8.1) or nothing will compile:
// MyProject.Build.cs
PublicDependencyModuleNames.AddRange(new string[]
{ "Core", "CoreUObject", "Engine", "InputCore", "UMG" }); // ← add UMG
Then a UUserWidget subclass. Create via New C++ Class → User Widget:
// MyHUDWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyHUDWidget.generated.h"
class UProgressBar;
class UTextBlock;
UCLASS(Abstract) // Abstract: use it via a Widget Blueprint subclass (Lesson 6.4)
class MYPROJECT_API UMyHUDWidget : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override; // the widget's "BeginPlay"
};
✅ Same pattern, new class
Mark the C++ widget Abstract and create a Widget Blueprint subclass of it (right-click the C++ class → but for widgets you reparent a WBP, or create the WBP with your class as its parent). The designer builds layout in the WBP; your C++ base holds logic. If this feels familiar, it's Lesson 6.4's AWeaponBase pattern applied to UI.
BindWidget
Here's the connective tissue. A designer places a Progress Bar named HealthBar and a Text Block named AmmoText in the Widget Blueprint. In C++, you declare pointers with meta = (BindWidget), and Unreal automatically links them by name at construction.
// MyHUDWidget.h — add bound widget pointers.
protected:
// The name MUST match the widget's name in the Widget Blueprint.
UPROPERTY(meta = (BindWidget))
TObjectPtr<UProgressBar> HealthBar;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> AmmoText;
// Optional binding — no error if the WBP lacks it:
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UTextBlock> ScoreText;
⚠️ Name match is mandatory (and case-sensitive)
With BindWidget, the C++ property name must exactly match the widget's name in the Widget Blueprint. If they don't match, the Blueprint won't compile — it errors that a required bound widget is missing. Use BindWidgetOptional when a widget might not exist in every subclass. This name-based binding is the reflection system again (Lesson 2.2) — it links by name at runtime.
📖 Now you have typed access
Once bound, HealthBar and AmmoText are ordinary pointers into the designer's layout. You call their API — HealthBar->SetPercent(...), AmmoText->SetText(...) — from C++, with full type safety. The designer owns where the bar is and how it looks; you own what value it shows.
Creating & Showing a Widget
A widget doesn't appear until you create an instance and add it to the viewport. This is typically done from the PlayerController (Lesson 4.3 — UI belongs to the player).
// In the PlayerController header:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
TSubclassOf<UMyHUDWidget> HUDWidgetClass; // designer assigns WBP_HUD
UPROPERTY()
TObjectPtr<UMyHUDWidget> HUDWidget; // the live instance (GC-safe, Lesson 2.5)
// In the PlayerController .cpp:
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
if (HUDWidgetClass)
{
// CreateWidget makes the instance; 'this' controller is its owner.
HUDWidget = CreateWidget<UMyHUDWidget>(this, HUDWidgetClass);
if (HUDWidget)
{
HUDWidget->AddToViewport(); // now it's on screen
}
}
}
✅ TSubclassOf again
TSubclassOf<UMyHUDWidget> (Lesson 3.4/6.3) lets the designer assign WBP_HUD in the editor — code doesn't hardcode which widget asset to use. CreateWidget instantiates it, AddToViewport shows it. Store the instance as a UPROPERTY so the GC keeps it alive and you can update or remove it later.
⚠️ Remove it when done
A widget added to the viewport stays until removed. Call HUDWidget->RemoveFromParent() when the UI should disappear (game over, menu closed). Leaving orphaned widgets on screen — or recreating them without removing the old ones — is a common UI leak.
Widget Lifecycle
Widgets have their own lifecycle hooks, analogous to an actor's (Lesson 3.2):
| Hook | Runs when | Use for |
|---|---|---|
NativeConstruct | The widget is constructed / added | Init, subscribe to events, first values (like BeginPlay) |
NativeDestruct | The widget is removed/destroyed | Unsubscribe, cleanup (like EndPlay) |
NativeTick | Every frame (if enabled) | Rarely — prefer events (next lesson) |
void UMyHUDWidget::NativeConstruct()
{
Super::NativeConstruct(); // always call Super (Lesson 1.5 discipline)
// Bound widgets are valid here — safe to set initial values.
if (HealthBar) { HealthBar->SetPercent(1.0f); }
if (AmmoText) { AmmoText->SetText(FText::FromString(TEXT("30 / 30"))); }
}
💡 NativeConstruct is where bindings are ready. Don't touch bound widgets in the C++ constructor — like an actor's constructor (Lesson 3.2), it runs too early, before the layout exists.NativeConstructis the widget's "the layout is built, go" moment — the parallel toBeginPlay. The next lesson uses it to subscribe to gameplay events.
Hands-on Exercise & Quiz
🏋️ Exercise: A HUD scaffold
Objective: Set up the widget and show it.
- Create
UPlayerHUD : public UUserWidget(Abstract) withBindWidgetpointers for aUProgressBar* HealthBarand aUTextBlock* AmmoText. - In the PlayerController, expose
TSubclassOf<UPlayerHUD> HUDClass, and inBeginPlaycreate it and add it to the viewport. - Set placeholder values in
NativeConstruct. - Why must the C++ pointer names match the Widget Blueprint widget names exactly?
✅ Answer to Q4
Because BindWidget links by name through the reflection system — Unreal finds the WBP widget whose name equals the C++ property name. A mismatch means it can't find the widget, so the Widget Blueprint fails to compile with a "missing bind widget" error. It's the same name-based reflection binding as AddDynamic in Module 5.
🎯 Quick Quiz
Question 1: What links a C++ pointer to a widget placed in a Widget Blueprint?
Question 2: Which puts a created widget on screen?
Question 3: Where can you first safely set values on bound widgets?
Summary
🎉 Key Takeaways
- UMG splits like C++/Blueprint: Widget Blueprint = visual layout, C++
UUserWidgetbase = logic — the Lesson 6.4 pattern for UI. - Add the
UMGmodule to.Build.cs; subclassUUserWidget(mark itAbstract). UPROPERTY(meta = (BindWidget))binds a C++ pointer to a same-named widget in the WBP (name match is mandatory);BindWidgetOptionalfor optional ones.- Show UI with
CreateWidget<T>→AddToViewport(from the PlayerController); store the instance as aUPROPERTYandRemoveFromParentwhen done. NativeConstructis the widget'sBeginPlay— the first safe place to touch bound widgets.
📚 Additional Resources
🚀 What's Next?
The HUD is on screen with placeholder values. Now make it live: the next lesson drives it from gameplay — subscribing to the OnHealthChanged and OnAmmoChanged events you built in Module 5, and handling button clicks.
🎉 Lesson complete!
Your UI exists. Let's make it react to the game.