ue-ui-umg-slate

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE UI: UMG, Slate, and Common UI

UE UI:UMG、Slate与Common UI

You are an expert in Unreal Engine's UI systems (UMG, Slate, and Common UI).
你是Unreal Engine UI系统(UMG、Slate和Common UI)方面的专家。

Context Check

上下文检查

Read
.agents/ue-project-context.md
to determine: which UI plugins are enabled (CommonUI, MVVM), target platforms (affects input method), whether this is multiplayer (widget ownership per player), and existing UI base class conventions.
阅读
.agents/ue-project-context.md
以确定:启用了哪些UI插件(CommonUI、MVVM)、目标平台(会影响输入方式)、是否为多人游戏(每个玩家拥有独立widget)以及现有的UI基类约定。

Information Gathering

信息收集

Before writing UI code, confirm: UI type (HUD, full-screen menu, modal, in-world WidgetComponent), input requirements (mouse, gamepad, keyboard), target platforms, and widget lifecycle (persistent, transient, or pooled).

编写UI代码前,请确认:UI类型(HUD、全屏菜单、模态窗口、世界空间WidgetComponent)、输入需求(鼠标、游戏手柄、键盘)、目标平台以及widget生命周期(持久化、临时或池化)。

1. UUserWidget in C++

1. C++中的UUserWidget

cpp
// MyWidget.h
UCLASS()
class MYGAME_API UMyWidget : public UUserWidget
{
    GENERATED_BODY()
protected:
    virtual void NativeConstruct() override; // Bind delegates here — BindWidget refs valid
    virtual void NativeDestruct() override;
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
};

// MyWidget.cpp
void UMyWidget::NativeConstruct()
{
    Super::NativeConstruct();
    if (PlayButton)
        PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
}
cpp
// MyWidget.h
UCLASS()
class MYGAME_API UMyWidget : public UUserWidget
{
    GENERATED_BODY()
protected:
    virtual void NativeConstruct() override; // Bind delegates here — BindWidget refs valid
    virtual void NativeDestruct() override;
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
};

// MyWidget.cpp
void UMyWidget::NativeConstruct()
{
    Super::NativeConstruct();
    if (PlayButton)
        PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
}

Lifecycle and GC

生命周期与垃圾回收(GC)

cpp
// Creation — always pass the owning PlayerController for player-UI
UMyWidget* Widget = CreateWidget<UMyWidget>(GetOwningPlayer(), MyWidgetClass);
Widget->AddToViewport(0);          // ZOrder: higher = on top. Roots the widget (won't GC).
Widget->AddToPlayerScreen(0);      // Split-screen: ties to a specific player's viewport region
Widget->RemoveFromParent();        // Un-roots — may be GC'd if no UPROPERTY holds it.

UPROPERTY() TObjectPtr<UMyWidget> CachedWidget; // Strong ref keeps alive after RemoveFromParent

// Visibility
Widget->SetVisibility(ESlateVisibility::Collapsed); // Not drawn, takes no space
Widget->SetVisibility(ESlateVisibility::Hidden);    // Not drawn, takes space
Widget->SetVisibility(ESlateVisibility::Visible);   // Drawn, receives input
// HitTestInvisible: drawn, passes input through; SelfHitTestInvisible: children receive input

cpp
// Creation — always pass the owning PlayerController for player-UI
UMyWidget* Widget = CreateWidget<UMyWidget>(GetOwningPlayer(), MyWidgetClass);
Widget->AddToViewport(0);          // ZOrder: higher = on top. Roots the widget (won't GC).
Widget->AddToPlayerScreen(0);      // Split-screen: ties to a specific player's viewport region
Widget->RemoveFromParent();        // Un-roots — may be GC'd if no UPROPERTY holds it.

UPROPERTY() TObjectPtr<UMyWidget> CachedWidget; // Strong ref keeps alive after RemoveFromParent

// Visibility
Widget->SetVisibility(ESlateVisibility::Collapsed); // Not drawn, takes no space
Widget->SetVisibility(ESlateVisibility::Hidden);    // Not drawn, takes space
Widget->SetVisibility(ESlateVisibility::Visible);   // Drawn, receives input
// HitTestInvisible: drawn, passes input through; SelfHitTestInvisible: children receive input

2. BindWidget Pattern

2. BindWidget模式

cpp
UCLASS()
class MYGAME_API UMyWidget : public UUserWidget
{
    GENERATED_BODY()
protected:
    UPROPERTY(meta=(BindWidget))             // Required — compiler warning if absent in UMG BP
    TObjectPtr<UButton> PlayButton;

    UPROPERTY(meta=(BindWidgetOptional))     // Optional — always null-check before use
    TObjectPtr<UTextBlock> SubtitleText;

    UPROPERTY(meta=(BindWidgetAnim), Transient) // Transient is required for anim bindings
    TObjectPtr<UWidgetAnimation> IntroAnim;

    UPROPERTY(meta=(BindWidget)) TObjectPtr<UTextBlock> ScoreText;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UImage> PlayerAvatar;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UProgressBar> HealthBar;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UListView> ItemList;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UScrollBox> ContentScroll; // Scrollable container
};
Name must match the UMG Blueprint widget name exactly (case-sensitive). Always null-check optional bindings.

cpp
UCLASS()
class MYGAME_API UMyWidget : public UUserWidget
{
    GENERATED_BODY()
protected:
    UPROPERTY(meta=(BindWidget))             // Required — compiler warning if absent in UMG BP
    TObjectPtr<UButton> PlayButton;

    UPROPERTY(meta=(BindWidgetOptional))     // Optional — always null-check before use
    TObjectPtr<UTextBlock> SubtitleText;

    UPROPERTY(meta=(BindWidgetAnim), Transient) // Transient is required for anim bindings
    TObjectPtr<UWidgetAnimation> IntroAnim;

    UPROPERTY(meta=(BindWidget)) TObjectPtr<UTextBlock> ScoreText;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UImage> PlayerAvatar;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UProgressBar> HealthBar;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UListView> ItemList;
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UScrollBox> ContentScroll; // Scrollable container
};
名称必须与UMG蓝图中的widget名称完全匹配(区分大小写)。对可选绑定务必进行空值检查。

3. Widget Interaction

3. Widget交互

UButton (Button.h)

UButton (Button.h)

cpp
// Delegates: OnClicked, OnPressed, OnReleased, OnHovered, OnUnhovered
PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
PlayButton->OnHovered.AddDynamic(this, &UMyWidget::HandlePlayHovered);
PlayButton->SetColorAndOpacity(FLinearColor(1.f, 0.8f, 0.f, 1.f));
PlayButton->SetIsEnabled(false);
// UE5.2+: WidgetStyle, ColorAndOpacity, ClickMethod direct access deprecated — use setters
cpp
// Delegates: OnClicked, OnPressed, OnReleased, OnHovered, OnUnhovered
PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
PlayButton->OnHovered.AddDynamic(this, &UMyWidget::HandlePlayHovered);
PlayButton->SetColorAndOpacity(FLinearColor(1.f, 0.8f, 0.f, 1.f));
PlayButton->SetIsEnabled(false);
// UE5.2+: WidgetStyle, ColorAndOpacity, ClickMethod direct access deprecated — use setters

UTextBlock (TextBlock.h)

UTextBlock (TextBlock.h)

cpp
// SetText wipes any Blueprint binding on Text
ScoreText->SetText(FText::Format(NSLOCTEXT("HUD", "ScoreFmt", "Score: {0}"), FText::AsNumber(Score)));
ScoreText->SetColorAndOpacity(FSlateColor(FLinearColor::White));
ScoreText->SetTextTransformPolicy(ETextTransformPolicy::ToUpper);
ScoreText->SetTextOverflowPolicy(ETextOverflowPolicy::Ellipsis);
cpp
// SetText wipes any Blueprint binding on Text
ScoreText->SetText(FText::Format(NSLOCTEXT("HUD", "ScoreFmt", "Score: {0}"), FText::AsNumber(Score)));
ScoreText->SetColorAndOpacity(FSlateColor(FLinearColor::White));
ScoreText->SetTextTransformPolicy(ETextTransformPolicy::ToUpper);
ScoreText->SetTextOverflowPolicy(ETextOverflowPolicy::Ellipsis);

UImage (Image.h)

UImage (Image.h)

cpp
PlayerAvatar->SetBrushFromTexture(AvatarTexture, /*bMatchSize=*/false);
PlayerAvatar->SetBrushFromMaterial(IconMaterial);
UMaterialInstanceDynamic* MID = PlayerAvatar->GetDynamicMaterial();
MID->SetScalarParameterValue(TEXT("Opacity"), 0.5f);
PlayerAvatar->SetBrushFromSoftTexture(SoftTextureRef, false); // Async stream
PlayerAvatar->SetColorAndOpacity(FLinearColor(1.f, 1.f, 1.f, 0.5f));
cpp
PlayerAvatar->SetBrushFromTexture(AvatarTexture, /*bMatchSize=*/false);
PlayerAvatar->SetBrushFromMaterial(IconMaterial);
UMaterialInstanceDynamic* MID = PlayerAvatar->GetDynamicMaterial();
MID->SetScalarParameterValue(TEXT("Opacity"), 0.5f);
PlayerAvatar->SetBrushFromSoftTexture(SoftTextureRef, false); // Async stream
PlayerAvatar->SetColorAndOpacity(FLinearColor(1.f, 1.f, 1.f, 0.5f));

UProgressBar (ProgressBar.h)

UProgressBar (ProgressBar.h)

cpp
HealthBar->SetPercent(CurrentHealth / MaxHealth); // 0.0–1.0
HealthBar->SetFillColorAndOpacity(FLinearColor(0.f, 1.f, 0.f, 1.f));
LoadingBar->SetIsMarquee(true); // Indeterminate animation
cpp
HealthBar->SetPercent(CurrentHealth / MaxHealth); // 0.0–1.0
HealthBar->SetFillColorAndOpacity(FLinearColor(0.f, 1.f, 0.f, 1.f));
LoadingBar->SetIsMarquee(true); // Indeterminate animation

UScrollBox (ScrollBox.h)

UScrollBox (ScrollBox.h)

UScrollBox
is a BindWidget-compatible container for scrollable content. Supports vertical and horizontal scroll. Add child widgets in Blueprint; query or modify scroll offset in C++:
cpp
ContentScroll->ScrollToEnd();
ContentScroll->SetScrollOffset(0.f);
ContentScroll->SetOrientation(Orient_Vertical); // default
UScrollBox
是支持BindWidget的可滚动内容容器,支持垂直和水平滚动。在蓝图中添加子widget;在C++中查询或修改滚动偏移:
cpp
ContentScroll->ScrollToEnd();
ContentScroll->SetScrollOffset(0.f);
ContentScroll->SetOrientation(Orient_Vertical); // default

UListView + UTileView + IUserObjectListEntry (ListView.h, TileView.h, IUserObjectListEntry.h)

UListView + UTileView + IUserObjectListEntry (ListView.h, TileView.h, IUserObjectListEntry.h)

UListView
— virtualised vertical list.
UTileView
— same interface, displays items in a grid layout (set tile width/height in the widget Designer panel). Both use
IUserObjectListEntry
.
cpp
// Entry widget must implement the interface
UCLASS()
class UItemEntryWidget : public UUserWidget, public IUserObjectListEntry
{
    GENERATED_BODY()
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UTextBlock> NameText;
protected:
    virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override
    {
        IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);
        if (UItemData* Data = Cast<UItemData>(ListItemObject))
            NameText->SetText(FText::FromString(Data->ItemName));
    }
};

// Populate
ItemList->ClearListItems();
for (const FItemInfo& Info : Items)
{
    UItemData* Data = NewObject<UItemData>(this);
    Data->ItemName = Info.Name;
    ItemList->AddItem(Data);
}
// Selection — NOTE: BP_OnItemClicked is Blueprint-only and private.
// For C++: override NativeOnItemClicked in a UListView subclass, or bind
// OnItemClickedInternal (protected delegate on UListViewBase).
UItemData* Selected = ItemList->GetSelectedItem<UItemData>();

UListView
——虚拟化垂直列表。
UTileView
——接口相同,以网格布局显示项(在widget设计器面板中设置 tile 宽/高)。两者均使用
IUserObjectListEntry
cpp
// Entry widget must implement the interface
UCLASS()
class UItemEntryWidget : public UUserWidget, public IUserObjectListEntry
{
    GENERATED_BODY()
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UTextBlock> NameText;
protected:
    virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override
    {
        IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);
        if (UItemData* Data = Cast<UItemData>(ListItemObject))
            NameText->SetText(FText::FromString(Data->ItemName));
    }
};

// Populate
ItemList->ClearListItems();
for (const FItemInfo& Info : Items)
{
    UItemData* Data = NewObject<UItemData>(this);
    Data->ItemName = Info.Name;
    ItemList->AddItem(Data);
}
// Selection — NOTE: BP_OnItemClicked is Blueprint-only and private.
// For C++: override NativeOnItemClicked in a UListView subclass, or bind
// OnItemClickedInternal (protected delegate on UListViewBase).
UItemData* Selected = ItemList->GetSelectedItem<UItemData>();

4. Input Mode Management

4. 输入模式管理

cpp
// UI-only — full-screen menus. Game input blocked.
FInputModeUIOnly UIMode;
UIMode.SetWidgetToFocus(Widget->TakeWidget());
GetOwningPlayer()->SetInputMode(UIMode);
GetOwningPlayer()->SetShowMouseCursor(true);

// Game-only — restore gameplay.
GetOwningPlayer()->SetInputMode(FInputModeGameOnly());
GetOwningPlayer()->SetShowMouseCursor(false);

// Game and UI — HUD tooltips, keeps game input active.
FInputModeGameAndUI GameUIMode;
GameUIMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockOnCapture);
GetOwningPlayer()->SetInputMode(GameUIMode);
GetOwningPlayer()->SetShowMouseCursor(true);
Set input mode from calling code (HUD/GameMode) after
AddToViewport
, not from
NativeConstruct
.

cpp
// UI-only — full-screen menus. Game input blocked.
FInputModeUIOnly UIMode;
UIMode.SetWidgetToFocus(Widget->TakeWidget());
GetOwningPlayer()->SetInputMode(UIMode);
GetOwningPlayer()->SetShowMouseCursor(true);

// Game-only — restore gameplay.
GetOwningPlayer()->SetInputMode(FInputModeGameOnly());
GetOwningPlayer()->SetShowMouseCursor(false);

// Game and UI — HUD tooltips, keeps game input active.
FInputModeGameAndUI GameUIMode;
GameUIMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockOnCapture);
GetOwningPlayer()->SetInputMode(GameUIMode);
GetOwningPlayer()->SetShowMouseCursor(true);
请在
AddToViewport
之后从调用代码(HUD/GameMode)设置输入模式,不要在
NativeConstruct
中设置。

5. Common UI (Plugin)

5. Common UI(插件)

See
references/common-ui-setup.md
for full plugin setup, GameViewportClient config, and layer architecture. Enable
CommonUI
and
CommonInput
plugins. Set viewport client to
UCommonGameViewportClient
in Project Settings > Maps & Modes > Game Viewport Client Class.
UCommonGameViewportClient
detects input method changes (gamepad vs mouse vs touch) and broadcasts them via
UCommonInputSubsystem
— this drives automatic gamepad/keyboard navigation and button prompt switching.
有关完整的插件设置、GameViewportClient配置和层级架构,请查看
references/common-ui-setup.md
。启用
CommonUI
CommonInput
插件。在项目设置>地图与模式>游戏视口客户端类中,将视口客户端设置为
UCommonGameViewportClient
UCommonGameViewportClient
会检测输入方式变化(游戏手柄/鼠标/触摸),并通过
UCommonInputSubsystem
广播这些变化——这会驱动自动的游戏手柄/键盘导航和按钮提示切换。

UCommonActivatableWidget (CommonActivatableWidget.h)

UCommonActivatableWidget (CommonActivatableWidget.h)

cpp
UCLASS()
class MYGAME_API UMyMenuScreen : public UCommonActivatableWidget
{
    GENERATED_BODY()
protected:
    virtual void NativeOnActivated() override;
    virtual void NativeOnDeactivated() override;
    virtual UWidget* NativeGetDesiredFocusTarget() const override { return CloseButton; }
    virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override
    {
        return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
    }
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonButtonBase> CloseButton;
};

void UMyMenuScreen::NativeOnActivated()
{
    Super::NativeOnActivated(); // Always call Super
    CloseButton->OnClicked().AddUObject(this, &UMyMenuScreen::DeactivateWidget);
}
void UMyMenuScreen::NativeOnDeactivated()
{
    CloseButton->OnClicked().RemoveAll(this);
    Super::NativeOnDeactivated(); // Always call Super
}
cpp
UCLASS()
class MYGAME_API UMyMenuScreen : public UCommonActivatableWidget
{
    GENERATED_BODY()
protected:
    virtual void NativeOnActivated() override;
    virtual void NativeOnDeactivated() override;
    virtual UWidget* NativeGetDesiredFocusTarget() const override { return CloseButton; }
    virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override
    {
        return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
    }
    UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonButtonBase> CloseButton;
};

void UMyMenuScreen::NativeOnActivated()
{
    Super::NativeOnActivated(); // Always call Super
    CloseButton->OnClicked().AddUObject(this, &UMyMenuScreen::DeactivateWidget);
}
void UMyMenuScreen::NativeOnDeactivated()
{
    CloseButton->OnClicked().RemoveAll(this);
    Super::NativeOnDeactivated(); // Always call Super
}

Widget Containers (CommonActivatableWidgetContainerBase.h)

Widget容器 (CommonActivatableWidgetContainerBase.h)

UCommonActivatableWidgetContainerBase
is the base class for Common UI widget containers. Two concrete subclasses:
  • UCommonActivatableWidgetStack
    — shows only the topmost widget; deactivating reveals the previous one (stack behaviour). This is the correct stack class — do NOT use
    UCommonActivatableWidgetSwitcher
    here, which inherits from
    UCommonAnimatedSwitcher
    /
    UWidgetSwitcher
    and is a different widget entirely.
  • UCommonActivatableWidgetQueue
    — FIFO queue; displays widgets sequentially, advancing to the next when the current deactivates.
cpp
UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonActivatableWidgetStack> MenuLayer;
UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonActivatableWidgetQueue> NotificationQueue;

// Push screen onto the stack — AddWidget creates the widget internally
UMyMenuScreen* Screen = Cast<UMyMenuScreen>(MenuLayer->AddWidget(UMyMenuScreen::StaticClass()));
Screen->ActivateWidget();

// Go back — deactivate re-activates the previous widget in the stack
Screen->DeactivateWidget();

// Queue a notification — plays after the current one finishes
NotificationQueue->AddWidget(UMyNotificationWidget::StaticClass());
UCommonActivatableWidgetContainerBase
是Common UI widget容器的基类,有两个具体子类:
  • UCommonActivatableWidgetStack
    ——仅显示最顶层的widget;关闭当前widget会显示上一个(栈行为)。这是正确的栈类——请勿使用
    UCommonActivatableWidgetSwitcher
    ,它继承自
    UCommonAnimatedSwitcher
    /
    UWidgetSwitcher
    ,是完全不同的widget。
  • UCommonActivatableWidgetQueue
    ——先进先出队列;按顺序显示widget,当前widget关闭后自动显示下一个。
cpp
UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonActivatableWidgetStack> MenuLayer;
UPROPERTY(meta=(BindWidget)) TObjectPtr<UCommonActivatableWidgetQueue> NotificationQueue;

// Push screen onto the stack — AddWidget creates the widget internally
UMyMenuScreen* Screen = Cast<UMyMenuScreen>(MenuLayer->AddWidget(UMyMenuScreen::StaticClass()));
Screen->ActivateWidget();

// Go back — deactivate re-activates the previous widget in the stack
Screen->DeactivateWidget();

// Queue a notification — plays after the current one finishes
NotificationQueue->AddWidget(UMyNotificationWidget::StaticClass());

UCommonButtonBase

UCommonButtonBase

Uses
FCommonButtonEvent
(declared via
DECLARE_EVENT
) — use
AddUObject
, not
AddDynamic
:
cpp
MyButton->OnClicked().AddUObject(this, &UMyScreen::HandleClick);
MyButton->OnClicked().RemoveAll(this); // In NativeOnDeactivated
使用
FCommonButtonEvent
(通过
DECLARE_EVENT
声明)——请使用
AddUObject
,而非
AddDynamic
cpp
MyButton->OnClicked().AddUObject(this, &UMyScreen::HandleClick);
MyButton->OnClicked().RemoveAll(this); // In NativeOnDeactivated

UCommonUIActionRouter

UCommonUIActionRouter

Handles input routing between game and UI layers; Common UI drives it automatically via
GetDesiredInputConfig
. Access directly when you need to query the current input mode:
cpp
#include "Input/CommonUIActionRouterBase.h"
// Get() takes a const UWidget& — call from within a widget (or pass any valid UWidget in scope):
UCommonUIActionRouterBase* Router = UCommonUIActionRouterBase::Get(*ContextWidget); // ContextWidget: const UWidget&
if (Router) { /* query active input config */ }
处理游戏与UI层之间的输入路由;Common UI会通过
GetDesiredInputConfig
自动驱动它。当你需要查询当前输入模式时,可直接访问它:
cpp
#include "Input/CommonUIActionRouterBase.h"
// Get() takes a const UWidget& — call from within a widget (or pass any valid UWidget in scope):
UCommonUIActionRouterBase* Router = UCommonUIActionRouterBase::Get(*ContextWidget); // ContextWidget: const UWidget&
if (Router) { /* query active input config */ }

Gamepad / Keyboard Navigation

游戏手柄/键盘导航

Common UI provides automatic gamepad and keyboard navigation without extra setup. Key behaviours:
  • UCommonButtonBase
    highlights automatically when it receives focus via D-pad or Tab navigation.
  • UCommonActivatableWidget::NativeGetDesiredFocusTarget()
    controls which widget receives focus when the screen activates.
  • When a widget deactivates, Common UI restores focus to the widget that was active before it opened — no manual focus bookkeeping required.
  • Bind
    GetDesiredInputConfig
    to
    ECommonInputMode::Menu
    to suppress game input while a menu is open.

Common UI无需额外设置即可提供自动的游戏手柄和键盘导航,核心行为包括:
  • UCommonButtonBase
    在通过方向键或Tab导航获得焦点时会自动高亮。
  • UCommonActivatableWidget::NativeGetDesiredFocusTarget()
    控制屏幕激活时哪个widget获得焦点。
  • 当widget关闭时,Common UI会自动将焦点恢复到打开前的widget——无需手动管理焦点。
  • GetDesiredInputConfig
    绑定到
    ECommonInputMode::Menu
    可在菜单打开时禁用游戏输入。

6. Slate Fundamentals

6. Slate基础

Use Slate for editor extensions, custom renderers, or when UMG doesn't expose required functionality.
SWidget
is the abstract base of all Slate widgets.
SCompoundWidget
(shown below) is the typical subclass for custom widgets.
cpp
// Custom widget — SMyWidget.h
class SMyWidget : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SMyWidget)
        : _LabelText(FText::GetEmpty()), _OnClicked() {}
        SLATE_ATTRIBUTE(FText, LabelText)
        SLATE_EVENT(FSimpleDelegate, OnClicked)
    SLATE_END_ARGS()

    void Construct(const FArguments& InArgs);
private:
    TAttribute<FText> LabelText;
    FSimpleDelegate OnClicked;
};

// SMyWidget.cpp
void SMyWidget::Construct(const FArguments& InArgs)
{
    LabelText = InArgs._LabelText;
    OnClicked = InArgs._OnClicked;
    ChildSlot
    [
        SNew(SButton)
        .OnClicked_Lambda([this]() -> FReply { OnClicked.ExecuteIfBound(); return FReply::Handled(); })
        [ SNew(STextBlock).Text(LabelText) ]
    ];
}

// Instantiation
TSharedRef<SMyWidget> W = SNew(SMyWidget).LabelText(NSLOCTEXT("UI", "Btn", "Go"));
TSharedPtr<SButton> Btn;
SAssignNew(Btn, SButton).OnClicked(FOnClicked::CreateUObject(this, &UMyClass::Handle));
Common Slate widgets:
STextBlock
,
SButton
,
SVerticalBox
,
SHorizontalBox
,
SOverlay
,
SBorder
,
SBox
,
SScrollBox
,
SImage
,
SEditableTextBox
.
Slate vs UMG: Use Slate for editor tools and maximum control. Use UMG for game runtime UI, Blueprint extensibility, animations, and BindWidget.

当需要开发编辑器扩展、自定义渲染器,或UMG未提供所需功能时,请使用Slate。
SWidget
是所有Slate widget的抽象基类。
SCompoundWidget
(如下所示)是自定义widget的典型子类。
cpp
// Custom widget — SMyWidget.h
class SMyWidget : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SMyWidget)
        : _LabelText(FText::GetEmpty()), _OnClicked() {}
        SLATE_ATTRIBUTE(FText, LabelText)
        SLATE_EVENT(FSimpleDelegate, OnClicked)
    SLATE_END_ARGS()

    void Construct(const FArguments& InArgs);
private:
    TAttribute<FText> LabelText;
    FSimpleDelegate OnClicked;
};

// SMyWidget.cpp
void SMyWidget::Construct(const FArguments& InArgs)
{
    LabelText = InArgs._LabelText;
    OnClicked = InArgs._OnClicked;
    ChildSlot
    [
        SNew(SButton)
        .OnClicked_Lambda([this]() -> FReply { OnClicked.ExecuteIfBound(); return FReply::Handled(); })
        [ SNew(STextBlock).Text(LabelText) ]
    ];
}

// Instantiation
TSharedRef<SMyWidget> W = SNew(SMyWidget).LabelText(NSLOCTEXT("UI", "Btn", "Go"));
TSharedPtr<SButton> Btn;
SAssignNew(Btn, SButton).OnClicked(FOnClicked::CreateUObject(this, &UMyClass::Handle));
常用Slate widget
STextBlock
SButton
SVerticalBox
SHorizontalBox
SOverlay
SBorder
SBox
SScrollBox
SImage
SEditableTextBox
Slate vs UMG:编辑器工具和需要最大控制时使用Slate;游戏运行时UI、蓝图扩展性、动画和BindWidget场景使用UMG。

7. MVVM (UE 5.1+, ModelViewViewModel plugin)

7. MVVM(UE 5.1+,ModelViewViewModel插件)

cpp
// ViewModel — MyGameViewModel.h
UCLASS()
class MYGAME_API UMyGameViewModel : public UMVVMViewModelBase
{
    GENERATED_BODY()
public:
    // UE_MVVM_SET_PROPERTY_VALUE: checks equality, sets, broadcasts field change
    void SetScore(int32 NewScore) { UE_MVVM_SET_PROPERTY_VALUE(Score, NewScore); }
    void SetPlayerName(FText NewName) { UE_MVVM_SET_PROPERTY_VALUE(PlayerName, NewName); }
    int32 GetScore() const { return Score; }
    FText GetPlayerName() const { return PlayerName; }
private:
    UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, meta=(AllowPrivateAccess))
    int32 Score = 0;
    UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, meta=(AllowPrivateAccess))
    FText PlayerName;
};

// From game code — UI updates automatically via field notifications:
void AMyPlayerState::OnScoreChanged(int32 NewScore) { ViewModel->SetScore(NewScore); }
To bind a ViewModel at runtime: create with
NewObject<UMyGameViewModel>(this)
, then configure in the widget Blueprint's Bindings panel. The
UMVVMView
component on the widget handles one-way and two-way property bindings automatically once the ViewModel class is set.

cpp
// ViewModel — MyGameViewModel.h
UCLASS()
class MYGAME_API UMyGameViewModel : public UMVVMViewModelBase
{
    GENERATED_BODY()
public:
    // UE_MVVM_SET_PROPERTY_VALUE: checks equality, sets, broadcasts field change
    void SetScore(int32 NewScore) { UE_MVVM_SET_PROPERTY_VALUE(Score, NewScore); }
    void SetPlayerName(FText NewName) { UE_MVVM_SET_PROPERTY_VALUE(PlayerName, NewName); }
    int32 GetScore() const { return Score; }
    FText GetPlayerName() const { return PlayerName; }
private:
    UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, meta=(AllowPrivateAccess))
    int32 Score = 0;
    UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, meta=(AllowPrivateAccess))
    FText PlayerName;
};

// From game code — UI updates automatically via field notifications:
void AMyPlayerState::OnScoreChanged(int32 NewScore) { ViewModel->SetScore(NewScore); }
要在运行时绑定ViewModel:使用
NewObject<UMyGameViewModel>(this)
创建,然后在widget蓝图的绑定面板中配置。widget上的
UMVVMView
组件会在设置ViewModel类后自动处理单向和双向属性绑定。

Common Mistakes

常见错误

Anti-patternCorrect approach
SetVisibility(Hidden)
to "hide" a menu
RemoveFromParent()
to stop rendering and input
CreateWidget(GetWorld(), ...)
for HUD widgets
CreateWidget(GetOwningPlayer(), ...)
SetInputMode(FInputModeUIOnly())
for a HUD
Use
FInputModeGameAndUI
or Common UI's
GetDesiredInputConfig
AddDynamic
with
UCommonButtonBase::OnClicked()
AddUObject
— it's
FCommonButtonEvent
not DYNAMIC
Binding delegates in
NativeTick
Bind once in
NativeConstruct
, unbind in
NativeDestruct
Skipping
Super::NativeOnActivated/Deactivated
Always call Super — it routes focus and input
One widget for all players in split-screen
AddToPlayerScreen
with per-player widgets
No UPROPERTY reference after
RemoveFromParent
(widget leak)
Hold a
UPROPERTY() TObjectPtr<>
to prevent GC, or call
RemoveFromParent()
to allow GC when no longer needed
Z-order conflictsMultiple widgets at same Z-order causes undefined draw order
Invisible widgets still tickHidden widgets with
NativeTick
consume CPU

反模式正确做法
使用
SetVisibility(Hidden)
来“隐藏”菜单
使用
RemoveFromParent()
停止渲染和输入
为HUD widget调用
CreateWidget(GetWorld(), ...)
调用
CreateWidget(GetOwningPlayer(), ...)
为HUD设置
SetInputMode(FInputModeUIOnly())
使用
FInputModeGameAndUI
或Common UI的
GetDesiredInputConfig
UCommonButtonBase::OnClicked()
使用
AddDynamic
使用
AddUObject
——它是
FCommonButtonEvent
而非动态委托
NativeTick
中绑定委托
NativeConstruct
中绑定一次,在
NativeDestruct
中解绑
跳过
Super::NativeOnActivated/Deactivated
务必调用父类方法——它负责焦点和输入路由
分屏游戏中所有玩家共用一个widget使用
AddToPlayerScreen
为每个玩家创建独立widget
RemoveFromParent
后无UPROPERTY引用(widget泄漏)
持有
UPROPERTY() TObjectPtr<>
以防止被GC回收,或在不再需要时调用
RemoveFromParent()
允许GC回收
Z-order冲突多个widget使用相同Z-order会导致绘制顺序不确定
不可见的widget仍在执行Tick带有
NativeTick
的隐藏widget会消耗CPU

Build.cs

Build.cs

csharp
PublicDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });
PrivateDependencyModuleNames.Add("CommonUI");      // If using CommonUI
PrivateDependencyModuleNames.Add("CommonInput");   // If using CommonInput
PrivateDependencyModuleNames.Add("ModelViewViewModel"); // If using MVVM

csharp
PublicDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });
PrivateDependencyModuleNames.Add("CommonUI");      // If using CommonUI
PrivateDependencyModuleNames.Add("CommonInput");   // If using CommonInput
PrivateDependencyModuleNames.Add("ModelViewViewModel"); // If using MVVM

Related Skills

相关技能

  • ue-cpp-foundations
    — UPROPERTY meta specifiers, TObjectPtr, module setup
  • ue-input-system
    — Enhanced Input, FInputModeXxx, input contexts
  • ue-editor-tools
    — Slate for editor detail panels and custom tools
  • ue-cpp-foundations
    ——UPROPERTY元说明符、TObjectPtr、模块设置
  • ue-input-system
    ——增强输入、FInputModeXxx、输入上下文
  • ue-editor-tools
    ——用于编辑器详情面板和自定义工具的Slate