SuperAPI AI Directory

ue-umg-and-slate

Coding & development

View repository

Updated 11-06 16:18

About this skill

UMG & Slate

UMG is Unreal's widget-based UI framework: a Widget Blueprint pairs with a C++ UUserWidget subclass that holds logic, while the Blueprint handles layout and styling. Underneath sits Slate, the lower-level C++ UI framework. Prefer UMG for game UI and CommonUI for anything with menus or multiplatform input routing.

When to use this skill

  • Creating a HUD, menu, inventory screen, or any on-screen UI.
  • Wiring UMG widgets to gameplay data or handling button/input events in C++.
  • Choosing how to create, show, and hide widgets at runtime.
  • Building a 3D in-world widget on an actor (UWidgetComponent).
  • Deciding between UMG, raw Slate, or CommonUI.
  • Diagnosing or preventing UI performance problems (draw calls, per-frame cost, hitches).
  • Structuring screens/menus for a real game (layer stacks, MVVM, resolution independence).

Mental model

  • UUserWidget — the C++ base for every Widget Blueprint. Holds logic, references to child widgets, and lifecycle callbacks.
  • Widget tree — a UWidgetTree (WidgetTree.h) owns all UWidget instances that make up the layout of a UUserWidget.
  • UWidget (Widget.h) — base of all UMG leaf and panel widgets; wraps a Slate SWidget.
  • Slate — the lower platform-level framework. SWidget (SlateCore) → SCompoundWidget → specialized widgets. UMG wraps Slate; you only author Slate for editor tools or bespoke widgets.

UUserWidget lifecycle

Declare in UserWidget.h:1582-1586.

Callback When Put here
NativeOnInitialized() Once, after the widget object is constructed and the widget tree is built — before it is ever shown. One-time setup that does not need a world (register non-world delegates, cache sub-widget refs).
NativePreConstruct() Every time the Widget Blueprint CDO is compiled or the designer refreshes. Design-time preview work only.
NativeConstruct() Widget is added to the viewport / displayed — analogous to BeginPlay. Bind OnClicked, start timers, fetch game state.
NativeDestruct() Widget is removed from the viewport. Clean up delegates/timers.
NativeTick(Geometry, DeltaTime) Per frame — only if tick is needed. Per-frame updates (prefer push-based updates instead).

UUserWidget has meta=(DisableNativeTick) on its UCLASS by default; override NativeTick only when genuinely needed or enable ticking explicitly.

The BindWidget pattern (core technique)

meta=(BindWidget) tells UMG that a UPROPERTY in C++ maps to a widget of the same name in the Widget Blueprint. The Blueprint compiler validates the name match at load time.

// MyHUDWidget.h
#pragma once
#include "Blueprint/UserWidget.h"
#include "MyHUDWidget.generated.h"

class UButton;
class UTextBlock;
class UProgressBar;

UCLASS()
class MYGAME_API UMyHUDWidget : public UUserWidget
{
    GENERATED_BODY()

protected:
    virtual void NativeConstruct() override;
    virtual void NativeDestruct() override;

    // Name in BP designer must match exactly:
    UPROPERTY(meta=(BindWidget))
    TObjectPtr<UButton>      StartButton;   // required — compile error if absent in BP

    UPROPERTY(meta=(BindWidget))
    TObjectPtr<UTextBlock>   ScoreText;

    UPROPERTY(meta=(BindWidgetOptional))
    TObjectPtr<UProgressBar> HealthBar;     // optional — null if not in BP, no error

    // Bound anim (Blueprint-authored UMG animation):
    UPROPERTY(meta=(BindWidgetAnim), Transient)
    TObjectPtr<UWidgetAnimation> FadeInAnim;

    UFUNCTION()
    void OnStartClicked();

public:
    void SetScore(int32 Score);
    void SetHealthPercent(float Percent);   // 0..1
};
// MyHUDWidget.cpp
#include "MyHUDWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Components/ProgressBar.h"

void UMyHUDWidget::NativeConstruct()
{
    Super::NativeConstruct();
    if (StartButton)
    {
        StartButton->OnClicked.AddDynamic(this, &UMyHUDWidget::OnStartClicked);
    }
}

void UMyHUDWidget::NativeDestruct()
{
    if (StartButton)
    {
        StartButton->OnClicked.RemoveDynamic(this, &UMyHUDWidget::OnStartClicked);
    }
    Super::NativeDestruct();
}

void UMyHUDWidget::OnStartClicked()
{
    // handle click
}

void UMyHUDWidget::SetScore(int32 Score)
{
    if (ScoreText)
    {
        ScoreText->SetText(FText::AsNumber(Score));  // FText for display/localization
    }
}

void UMyHUDWidget::SetHealthPercent(float Percent)
{
    if (HealthBar)
    {
        HealthBar->SetPercent(FMath::Clamp(Percent, 0.f, 1.f));
    }
}

Key rules:

  • The C++ variable name must equal the widget name in the Blueprint designer; any mismatch is a load/compile error (use BindWidgetOptional if the widget may not exist).
  • OnClicked is a UPROPERTY(BlueprintAssignable) dynamic multicast delegate on UButton (Button.h:76); the bound function must be a UFUNCTION().
  • Remove AddDynamic bindings in NativeDestruct to prevent stale delegates.

Creating and showing widgets at runtime

// In a PlayerController or HUD:
UPROPERTY(EditAnywhere, Category="UI")
TSubclassOf<UMyHUDWidget> HUDWidgetClass;   // assign the Widget BP in the editor

UPROPERTY()
TObjectPtr<UMyHUDWidget> HUDWidget;

void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();

    // CreateWidget<T>(owner, class) — owner can be World, PlayerController, or GameInstance
    HUDWidget = CreateWidget<UMyHUDWidget>(this, HUDWidgetClass);
    if (HUDWidget)
    {
        HUDWidget->AddToViewport();          // ZOrder 0 by default
        // HUDWidget->AddToPlayerScreen();   // split-screen: add to a player's viewport slice
    }
}

void AMyPlayerController::HideHUD()
{
    if (HUDWidget)
    {
        HUDWidget->RemoveFromParent();       // hides; does not destroy (still held by UPROPERTY)
    }
}
  • CreateWidget is a templated free function (UserWidget.h:1819); it accepts a UWorld*, APlayerController*, UGameInstance*, UWidget*, or UWidgetTree* as owner.
  • Hold widget pointers in a UPROPERTY() — without it the GC destroys the widget even if it is displayed (ue-memory-and-gc).
  • RemoveFromViewport is deprecated since 5.1; use RemoveFromParent() instead.

Data binding & update strategy

Approach When
Push setters (recommended) Call SetScore()/SetHealthPercent() from gameplay code when data changes — zero per-frame cost.
UMG Property Binding (legacy) A function returning a value bound in the editor; re-evaluates every frame — avoid for many widgets.
MVVM Viewmodel plugin Declare UMVVMViewModelBase with FieldNotify properties; view bindings update only on change. Best for larger data-driven UIs (UE 5.1+, still Beta in 5.8).

Epic's hard rule: never raw property bindings — they poll every frame per widget. Drive updates from gameplay delegates, and pull initial state once in NativeConstruct. Full MVVM walkthrough (FieldNotify, UE_MVVM_SET_PROPERTY_VALUE, viewmodel design): references/architecture-and-authoring.md.

Performance best practices (summary)

The rules agents most often need — deep dive with engine citations in references/performance-and-best-practices.md:

  • One Canvas Panel at the root is fine; never nest Canvas Panels inside reusable widgets (each canvas child gets its own layer ID → extra draw calls, broken batching). Build templates from Overlay/HorizontalBox/VerticalBox/GridPanel.
  • No Tick, no OnPaint, no property bindings — event-driven updates only.
  • Wrap rarely-changing subtrees in UInvalidationBox; mark per-frame widgets Volatile; reach for URetainerBox (phased render-to-texture) only after that.
  • Pool dynamic entries: UListView/UTileView (virtualized + pooled) for lists, FUserWidgetPool for damage numbers/markers/toasts.
  • Hidden widgets still load and construct — every WidgetSwitcher page, every TSubclassOf reference. Delete unused widgets; async-load rare screens via soft refs.
  • Prefer Collapsed over Hidden (skips layout); set decorative widgets to HitTestInvisible; don't call SetVisibility/FText::Format per frame.
  • USpacer over USizeBox for spacing; never Scale Box + Size Box together (per-frame layout flip-flop); Rich Text only when really needed.
  • Animation cost tiers: material animation > BP tween > Sequencer anim > anything that changes layout.
  • Profile with stat Slate, Widget Reflector (Ctrl+Shift+W), Slate.ShowBatching, stat dumpframe -ms=0.1.

Input, focus, and input mode

  • Override NativeOnKeyDown, NativeOnMouseButtonDown, etc. for per-widget input handling.
  • For menus: switch input mode on the PlayerController via SetInputModeUIOnly/SetInputModeGameAndUI/SetInputModeGameOnly and toggle the cursor.
  • For gamepad/multiplatform menus, see CommonUI below — plain UMG focus is painful to manage manually across platforms.

UWidgetComponent — 3D in-world widgets

UWidgetComponent (WidgetComponent.h:95) is a UMeshComponent that renders a UUserWidget onto a render target, then displays it on a plane or cylinder in 3D space.

// In an actor's .h:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UWidgetComponent> InteractPrompt;

// In constructor:
InteractPrompt = CreateDefaultSubobject<UWidgetComponent>(TEXT("InteractPrompt"));
InteractPrompt->SetupAttachment(RootComponent);
InteractPrompt->SetWidgetClass(PromptWidgetClass);   // TSubclassOf<UUserWidget>
InteractPrompt->SetDrawSize(FVector2D(200.f, 80.f));
InteractPrompt->SetWidgetSpace(EWidgetSpace::World); // World or Screen

Access the live widget instance at runtime: InteractPrompt->GetWidget(). For player interaction with 3D widgets, pair with UWidgetInteractionComponent.

CommonUI (recommended for production menus)

The CommonUI plugin (Engine/Plugins/Runtime/CommonUI/) builds on UMG for structured, multiplatform UI:

  • UCommonUserWidget — UUserWidget + input-action binding (CommonUserWidget.h:33).
  • UCommonActivatableWidget — adds activate/deactivate semantics and a back-navigation stack; the widget can turn on/off without being removed from the hierarchy (CommonActivatableWidget.h:43).
  • Input routing — only the topmost active activatable widget in a layer receives input; no need to manually manage focus across multiple overlapping menus.
  • Style assets — UCommonButtonBase, UCommonTextBlock, etc., separate style data from widget instances, making platform-specific theming practical.

Use CommonUI for any UI with menus, modals, or gamepad navigation. Base game-HUD widgets on UCommonUserWidget; base menus on UCommonActivatableWidget.

Screen architecture — layer stacks (the Lyra pattern): production games register one root layout with named layers, each a UCommonActivatableWidgetStack (or ...Queue for modals) — e.g. UI.Layer.Game / GameMenu / Menu / Modal in Lyra's PrimaryGameLayout. Screens are pushed/popped by layer tag instead of AddToViewport, which gives correct Z-order, input routing to the topmost active widget, and free back-button handling. Details and best practices: references/architecture-and-authoring.md.

Slate layer (when and how)

UMG wraps Slate. For editor tools and highly bespoke widgets not achievable in UMG, author Slate directly.

// Minimal SCompoundWidget subclass — editor tool or plugin UI:
class SMyPanel : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SMyPanel) {}
        SLATE_ARGUMENT(FText, LabelText)
    SLATE_END_ARGS()

    void Construct(const FArguments& InArgs)
    {
        ChildSlot
        [
            SNew(STextBlock).Text(InArgs._LabelText)
        ];
    }
};

Core Slate types:

  • SWidget (SlateCore/Public/Widgets/SWidget.h) — base; pure abstract (OnPaint:1771, ComputeDesiredSize:774, GetChildren:899).
  • SCompoundWidget (SCompoundWidget.h) — single ChildSlot; base for most authored widgets.
  • SLeafWidget — no children; for custom-drawn leaf elements.
  • SNew(WidgetType) / SAssignNew(Ptr, WidgetType) — declarative construction macros (DeclarativeSyntaxSupport.h:37).

For game UI, stay in UMG/CommonUI — Slate skips UPROPERTY/GC and needs more boilerplate.

Gotchas

  • BindWidget name mismatch — the C++ variable name must match the Blueprint widget name exactly; a mismatch is a compiler/load error. Use BindWidgetOptional if the widget may be absent. (Widget.h:69-74).
  • Widget not in a UPROPERTY — the GC destroys it even if still displayed; always hold in a UPROPERTY() member.
  • RemoveFromViewport deprecated (5.1+) — use RemoveFromParent().
  • Per-frame property bindings across many widgets → significant CPU cost; push updates from gameplay instead.
  • Canvas Panels nested inside reusable widgets — each canvas child gets its own layer ID → extra draw calls across the whole screen. One canvas at the root only; templates use Overlay/boxes.
  • Scale Box wrapping a Size Box (or vice versa) — the two fight over desired size and can flip layout every frame; let content size itself or pick one.
  • Calling SetVisibility every frame — it's surprisingly expensive (can invalidate layout); only call on actual state change. Prefer Collapsed over Hidden, and HitTestInvisible for decorations.
  • Hidden widgets still load and construct — unused widgets in the tree and every page of a WidgetSwitcher pay full load/construct cost; delete leftovers, async-load rare screens.
  • Create/destroy churn for dynamic entries — use UListView or FUserWidgetPool instead; with FUserWidgetPool, call ReleaseAllSlateResources() from the owner's ReleaseSlateResources or the pool leaks via circular refs.
  • OnClicked handler not a UFUNCTION() — AddDynamic silently fails; the bound method must be marked UFUNCTION() with the matching signature.
  • Forgetting SetInputMode/cursor — menus added to viewport that don't receive clicks usually have the wrong input mode on the owning PlayerController.
  • Gamepad/focus pain with plain UMG — use CommonUI for structured input routing across menus and platforms (ue-enhanced-input for input-action integration).
  • Building display text with FString — use FText::AsNumber, FText::Format, etc. for localization (ue-core-types-and-containers).
  • WidgetComponent not visible — ensure DrawSize is non-zero and collision/visibility settings are correct; EWidgetSpace::Screen ignores world occlusion.
  • NativeTick never called — UUserWidget has DisableNativeTick by default; the tick only runs if you override NativeTick and the widget has latent actions or Blueprint tick is set to Auto (EWidgetTickFrequency:UserWidget.h:117).

Version notes

  • RemoveFromViewport deprecated in 5.1; replaced by RemoveFromParent (from UWidget).
  • UTextBlock/UButton direct property access deprecated in 5.1/5.2 respectively; use getter/setter methods (GetText()/SetText(), GetStyle()/SetStyle()).
  • UMG Viewmodel (MVVM) plugin available since 5.1, still Beta in 5.8; stable enough for production with care.
  • CommonUI ships in engine and is stable; Lyra uses it as the canonical game UI reference.

References & source material

Engine source (UE 5.8, under Engine/Source/):

  • Runtime/UMG/Public/Blueprint/UserWidget.h — UUserWidget UCLASS:280, AddToViewport:342, AddToPlayerScreen:351, RemoveFromViewport (deprecated 5.1):358, NativeOnInitialized:1582, NativePreConstruct:1583, NativeConstruct:1584, NativeDestruct:1585, NativeTick:1586, CreateWidgetInstance (backing CreateWidget):1470-1474, EWidgetTickFrequency:117.
  • Runtime/UMG/Public/Blueprint/WidgetTree.h — UWidgetTree, RootWidget:150, ConstructWidget<T>:106, ForEachWidget:78, FindWidget:34.
  • Runtime/UMG/Public/Components/Widget.h — UWidget UCLASS:216, BindWidget/BindWidgetOptional metadata enum:69-74.
  • Runtime/UMG/Public/Components/Button.h — UButton:32, OnClicked delegate:76, OnPressed:80, OnReleased:84, SetStyle:117, GetStyle:119.
  • Runtime/UMG/Public/Components/TextBlock.h — UTextBlock:23, SetText/GetText (via Getter/Setter specifiers):31.
  • Runtime/UMG/Public/Components/PanelWidget.h — UPanelWidget:14, AddChild:59, GetChildAt:36, GetChildrenCount:28.
  • Runtime/UMG/Public/Components/WidgetComponent.h — UWidgetComponent:95, EWidgetSpace:25, GetWidget:207, SetWidget:214, SetWidgetClass:338.
  • Runtime/SlateCore/Public/Widgets/SWidget.h — SWidget, ComputeDesiredSize:774, GetChildren:899, OnPaint:1771.
  • Runtime/SlateCore/Public/Widgets/SCompoundWidget.h — SCompoundWidget:21, ChildSlot:113.
  • Runtime/SlateCore/Public/Widgets/DeclarativeSyntaxSupport.h — SNew:37, SAssignNew:41.
  • Engine/Plugins/Runtime/CommonUI/Source/CommonUI/Public/CommonUserWidget.h — UCommonUserWidget:33.
  • Engine/Plugins/Runtime/CommonUI/Source/CommonUI/Public/CommonActivatableWidget.h — UCommonActivatableWidget:43, ActivateWidget:52, DeactivateWidget:55.

Official docs (UE 5.8, verified):

Deep-dive references in this skill:

  • references/userwidget-and-binding.md — full UUserWidget lifecycle, BindWidget internals, NativeOnInitialized vs NativeConstruct, animation binding (BindWidgetAnim), and GameViewportSubsystem.
  • references/common-widgets-and-layout.md — every common leaf widget and panel type, slot system, dynamic widget creation from C++.
  • references/widget-component-and-input.md — UWidgetComponent deep dive, UWidgetInteractionComponent, input mode management, focus.
  • references/slate-layer.md — SWidget hierarchy, declarative syntax, SCompoundWidget authoring, Invalidation, and when to use Slate vs UMG.
  • references/performance-and-best-practices.md — invalidation boxes/retainer boxes/volatility, event-driven updates vs property bindings, widget pooling (FUserWidgetPool, ListView), loading/construction costs, Canvas Panel and layout do/don'ts, animation cost tiers, hidden costs (SetVisibility, FText::Format), and the profiling toolbox.
  • references/architecture-and-authoring.md — CommonUI activatable layer stacks (the Lyra pattern), decoupling widgets from gameplay, MVVM viewmodels (FieldNotify, UE_MVVM_SET_PROPERTY_VALUE), DPI scaling and resolution independence, safe zones, and texture/slot-sizing authoring rules.