ue-ui-umg-slate
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE 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 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基类约定。
.agents/ue-project-context.mdInformation 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 inputcpp
// 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 input2. 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 setterscpp
// 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 settersUTextBlock (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 animationcpp
HealthBar->SetPercent(CurrentHealth / MaxHealth); // 0.0–1.0
HealthBar->SetFillColorAndOpacity(FLinearColor(0.f, 1.f, 0.f, 1.f));
LoadingBar->SetIsMarquee(true); // Indeterminate animationUScrollBox (ScrollBox.h)
UScrollBox (ScrollBox.h)
UScrollBoxcpp
ContentScroll->ScrollToEnd();
ContentScroll->SetScrollOffset(0.f);
ContentScroll->SetOrientation(Orient_Vertical); // defaultUScrollBoxcpp
ContentScroll->ScrollToEnd();
ContentScroll->SetScrollOffset(0.f);
ContentScroll->SetOrientation(Orient_Vertical); // defaultUListView + UTileView + IUserObjectListEntry (ListView.h, TileView.h, IUserObjectListEntry.h)
UListView + UTileView + IUserObjectListEntry (ListView.h, TileView.h, IUserObjectListEntry.h)
UListViewUTileViewIUserObjectListEntrycpp
// 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>();UListViewUTileViewIUserObjectListEntrycpp
// 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 , not from .
AddToViewportNativeConstructcpp
// 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);请在之后从调用代码(HUD/GameMode)设置输入模式,不要在中设置。
AddToViewportNativeConstruct5. Common UI (Plugin)
5. Common UI(插件)
See for full plugin setup, GameViewportClient config, and layer architecture. Enable and plugins. Set viewport client to in Project Settings > Maps & Modes > Game Viewport Client Class. detects input method changes (gamepad vs mouse vs touch) and broadcasts them via — this drives automatic gamepad/keyboard navigation and button prompt switching.
references/common-ui-setup.mdCommonUICommonInputUCommonGameViewportClientUCommonGameViewportClientUCommonInputSubsystem有关完整的插件设置、GameViewportClient配置和层级架构,请查看。启用和插件。在项目设置>地图与模式>游戏视口客户端类中,将视口客户端设置为。会检测输入方式变化(游戏手柄/鼠标/触摸),并通过广播这些变化——这会驱动自动的游戏手柄/键盘导航和按钮提示切换。
references/common-ui-setup.mdCommonUICommonInputUCommonGameViewportClientUCommonGameViewportClientUCommonInputSubsystemUCommonActivatableWidget (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- — shows only the topmost widget; deactivating reveals the previous one (stack behaviour). This is the correct stack class — do NOT use
UCommonActivatableWidgetStackhere, which inherits fromUCommonActivatableWidgetSwitcher/UCommonAnimatedSwitcherand is a different widget entirely.UWidgetSwitcher - — FIFO queue; displays widgets sequentially, advancing to the next when the current deactivates.
UCommonActivatableWidgetQueue
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- ——仅显示最顶层的widget;关闭当前widget会显示上一个(栈行为)。这是正确的栈类——请勿使用
UCommonActivatableWidgetStack,它继承自UCommonActivatableWidgetSwitcher/UCommonAnimatedSwitcher,是完全不同的widget。UWidgetSwitcher - ——先进先出队列;按顺序显示widget,当前widget关闭后自动显示下一个。
UCommonActivatableWidgetQueue
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 (declared via ) — use , not :
FCommonButtonEventDECLARE_EVENTAddUObjectAddDynamiccpp
MyButton->OnClicked().AddUObject(this, &UMyScreen::HandleClick);
MyButton->OnClicked().RemoveAll(this); // In NativeOnDeactivated使用(通过声明)——请使用,而非:
FCommonButtonEventDECLARE_EVENTAddUObjectAddDynamiccpp
MyButton->OnClicked().AddUObject(this, &UMyScreen::HandleClick);
MyButton->OnClicked().RemoveAll(this); // In NativeOnDeactivatedUCommonUIActionRouter
UCommonUIActionRouter
Handles input routing between game and UI layers; Common UI drives it automatically via . Access directly when you need to query the current input mode:
GetDesiredInputConfigcpp
#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会通过自动驱动它。当你需要查询当前输入模式时,可直接访问它:
GetDesiredInputConfigcpp
#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:
- highlights automatically when it receives focus via D-pad or Tab navigation.
UCommonButtonBase - controls which widget receives focus when the screen activates.
UCommonActivatableWidget::NativeGetDesiredFocusTarget() - When a widget deactivates, Common UI restores focus to the widget that was active before it opened — no manual focus bookkeeping required.
- Bind to
GetDesiredInputConfigto suppress game input while a menu is open.ECommonInputMode::Menu
Common UI无需额外设置即可提供自动的游戏手柄和键盘导航,核心行为包括:
- 在通过方向键或Tab导航获得焦点时会自动高亮。
UCommonButtonBase - 控制屏幕激活时哪个widget获得焦点。
UCommonActivatableWidget::NativeGetDesiredFocusTarget() - 当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.
SWidgetSCompoundWidgetcpp
// 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: , , , , , , , , , .
STextBlockSButtonSVerticalBoxSHorizontalBoxSOverlaySBorderSBoxSScrollBoxSImageSEditableTextBoxSlate vs UMG: Use Slate for editor tools and maximum control. Use UMG for game runtime UI, Blueprint extensibility, animations, and BindWidget.
当需要开发编辑器扩展、自定义渲染器,或UMG未提供所需功能时,请使用Slate。
SWidgetSCompoundWidgetcpp
// 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:、、、、、、、、、。
STextBlockSButtonSVerticalBoxSHorizontalBoxSOverlaySBorderSBoxSScrollBoxSImageSEditableTextBoxSlate 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 , then configure in the widget Blueprint's Bindings panel. The component on the widget handles one-way and two-way property bindings automatically once the ViewModel class is set.
NewObject<UMyGameViewModel>(this)UMVVMViewcpp
// 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:使用创建,然后在widget蓝图的绑定面板中配置。widget上的组件会在设置ViewModel类后自动处理单向和双向属性绑定。
NewObject<UMyGameViewModel>(this)UMVVMViewCommon Mistakes
常见错误
| Anti-pattern | Correct approach |
|---|---|
| |
| |
| Use |
| |
Binding delegates in | Bind once in |
Skipping | Always call Super — it routes focus and input |
| One widget for all players in split-screen | |
No UPROPERTY reference after | Hold a |
| Z-order conflicts | Multiple widgets at same Z-order causes undefined draw order |
| Invisible widgets still tick | Hidden widgets with |
| 反模式 | 正确做法 |
|---|---|
使用 | 使用 |
为HUD widget调用 | 调用 |
为HUD设置 | 使用 |
为 | 使用 |
在 | 在 |
跳过 | 务必调用父类方法——它负责焦点和输入路由 |
| 分屏游戏中所有玩家共用一个widget | 使用 |
| 持有 |
| Z-order冲突 | 多个widget使用相同Z-order会导致绘制顺序不确定 |
| 不可见的widget仍在执行Tick | 带有 |
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 MVVMcsharp
PublicDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });
PrivateDependencyModuleNames.Add("CommonUI"); // If using CommonUI
PrivateDependencyModuleNames.Add("CommonInput"); // If using CommonInput
PrivateDependencyModuleNames.Add("ModelViewViewModel"); // If using MVVMRelated Skills
相关技能
- — UPROPERTY meta specifiers, TObjectPtr, module setup
ue-cpp-foundations - — Enhanced Input, FInputModeXxx, input contexts
ue-input-system - — Slate for editor detail panels and custom tools
ue-editor-tools
- ——UPROPERTY元说明符、TObjectPtr、模块设置
ue-cpp-foundations - ——增强输入、FInputModeXxx、输入上下文
ue-input-system - ——用于编辑器详情面板和自定义工具的Slate
ue-editor-tools