mcpField
U
ue-ui-umg-slate

ue-ui-umg-slate

github.com/quodsoler开发4191.4k 次安装4 周前更新
代码仓库

关于 ue-ui-umg-slate

面向 Unreal Engine 5.8 的 UI 开发技能指南,适用于使用 UMG、Slate 和 Common UI 构建界面的开发者。内容涵盖 Widget 生命周期、交互、动画、输入焦点、列表与 MVVM。

主要功能

  1. 01Widget 生命周期与创建
  2. 02UMG 控件交互和列表
  3. 03Widget 动画与输入焦点
  4. 04Common UI 激活栈和输入路由
  5. 05Slate 与 MVVM 指引

适用场景

  1. 01创建并管理运行时 Widget
  2. 02绑定按钮并展示文本和进度
  3. 03构建菜单屏幕与激活栈
  4. 04实现数据绑定或自定义 Slate 控件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-ui-umg-slate

安装后在 Claude Code 中输入/ue-ui-umg-slate确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
common-ui-setup.md
slate-patterns.md
widget-types.md

详细介绍

UE UI:UMG、Slate 和 Common UI

目标引擎:UE 5.8。以下 API 已对照 5.8 头文件验证;旧版形式列在“已弃用 — 请勿使用”下。

UMG 是运行时 UI 框架(UMG 模块,Engine/Source/Runtime/UMG/Public),构建于 Slate(Slate、SlateCore)之上。Common UI(插件,5.8 中已用于生产环境)增加了跨平台输入路由、激活堆栈和适配平台的按钮。Model View Viewmodel(插件,5.8 中为 Beta)在 FieldNotification 之上增加了声明式数据绑定。

上下文

如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果该文件不存在,也请继续。

根据请求和代码库确定相关领域。只有在两种合理解读会导致代码不同时才询问。

请求涉及……转到
Widget 类、重写、创建、可见性、GCWidget 生命周期
将设计器中的 Widget 连接到 C++(meta=(BindWidget))BindWidget
按钮、文本、图像、进度条、列表Widget 交互
播放/停止 UMG 动画Widget 动画
鼠标光标、焦点、SetInputMode输入模式和焦点
菜单、屏幕堆栈、游戏手柄、返回操作Common UI
数据绑定、视图模型、字段通知MVVM
在关卡中渲染的 UI世界空间 UI
遍历 Widget、拖放、画刷、几何体Widget 树和辅助库
自定义 SWidget、SNew、SLATE_BEGIN_ARGSSlate

各 Widget 的方法表(setter、委托、枚举)见 references/widget-types.md。完整的 Common UI 插件设置见 references/common-ui-setup.md。完整的自定义 Slate Widget 示例见 references/slate-patterns.md。

Widget 生命周期

// MyWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyWidget.generated.h"

class UButton;

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

protected:
    virtual void NativeOnInitialized() override;
    virtual void NativePreConstruct() override;
    virtual void NativeConstruct() override;
    virtual void NativeDestruct() override;
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;

    UPROPERTY(meta=(BindWidget))
    TObjectPtr<UButton> PlayButton;

    UFUNCTION()
    void HandlePlayClicked();
};

// MyWidget.cpp — #include "MyWidget.h" and "Components/Button.h"
void UMyWidget::NativeOnInitialized() { Super::NativeOnInitialized(); }
void UMyWidget::NativePreConstruct() { Super::NativePreConstruct(); }
void UMyWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
    Super::NativeTick(MyGeometry, InDeltaTime);
}

void UMyWidget::NativeConstruct()
{
    Super::NativeConstruct();       // Runs on every re-add; pair each bind with an unbind in NativeDestruct
    PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
}

void UMyWidget::NativeDestruct()
{
    PlayButton->OnClicked.RemoveDynamic(this, &UMyWidget::HandlePlayClicked);
    Super::NativeDestruct();
}
重写(摘自 Blueprint/UserWidget.h)调用时机
virtual void NativeOnInitialized() override;每个实例调用一次,在 Widget 对象构造完成后调用
virtual void NativePreConstruct() override;Slate 树重建后、NativeConstruct 之前调用(UserWidget.cpp:1231);在设计器中也会调用
virtual void NativeConstruct() override;Slate Widget 添加到父级时调用 — 在此处绑定委托
virtual void NativeDestruct() override;Slate Widget 从父级移除时调用 — 在此处解除绑定
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;每帧调用一次(在 Tick 期间)
virtual int32 NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;自定义绘制;返回最后使用的图层 ID
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;按下指针时调用
virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;获得焦点时按下按键调用
virtual FReply NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent) override;此 Widget 获得焦点时调用
virtual void NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent) override;此 Widget 或其子级获得焦点时调用
virtual bool Initialize() override;构建 Widget 树;调用 Super::Initialize() 并返回其结果
virtual TSharedRef<SWidget> RebuildWidget() override;构建底层 Slate Widget
virtual void ReleaseSlateResources(bool bReleaseChildren) override;在此处释放缓存的 TSharedPtr<SWidget> 成员

输入处理函数返回 FReply::Handled() 以使用该事件,或返回 FReply::Unhandled() 以允许事件向上冒泡。

创建、视口、可见性
// CreateWidget<T>(Owner, Class, WidgetName = NAME_None). Owner may be a UWidget,
// UWidgetTree, APlayerController, UGameInstance or UWorld.
UMyWidget* Widget = CreateWidget<UMyWidget>(GetOwningPlayer(), MyWidgetClass);
Widget->AddToViewport(0);       // ZOrder: higher draws on top. Roots the widget.
Widget->AddToPlayerScreen(0);   // Split-screen: one player's viewport region; returns bool
Widget->RemoveFromParent();     // Un-roots - collectable unless a UPROPERTY holds it
Widget->SetVisibility(ESlateVisibility::Collapsed);  // Not drawn, takes no space
Widget->SetVisibility(ESlateVisibility::Hidden);     // Not drawn, still takes space
Widget->SetVisibility(ESlateVisibility::Visible);    // Drawn, hit-testable
Widget->SetIsEnabled(false);                         // Greys out, blocks input

HitTestInvisible 会绘制内容,但会让该控件及其子控件的输入事件穿透;SelfHitTestInvisible 只会让该控件的输入事件穿透。如果控件在调用 RemoveFromParent 后仍需保留,请持有一个 UPROPERTY() TObjectPtr<UMyWidget>。GetOwningPlayer() 会返回所属的 APlayerController;GetOwningPlayer<AMyPlayerController>() 会为你进行类型转换。

BindWidget

UPROPERTY(meta=(BindWidget))                 // Compile error in the Widget BP if missing
TObjectPtr<UTextBlock> ScoreText;
UPROPERTY(meta=(BindWidgetOptional))         // May be null - always null-check
TObjectPtr<UTextBlock> SubtitleText;
UPROPERTY(Transient, meta=(BindWidgetAnim))  // Transient is required for animation bindings
TObjectPtr<UWidgetAnimation> IntroAnim;

规则:C++ 成员名称必须与 Widget Blueprint 中的控件名称完全匹配(区分大小写);声明的类型必须与设计器控件的类相同,或是其基类;绑定成员会在 Initialize() 中、NativeOnInitialized 运行之前赋值(UserWidget.cpp:153,175),因此在 NativeOnInitialized、NativePreConstruct 和 NativeConstruct 中均有效——应在 NativeOnInitialized 中绑定每个实例只需绑定一次的委托。将它们声明为 protected,且不指定 Category。

控件交互

UMG 组件上的动态多播委托使用 AddDynamic,因此处理函数必须是 UFUNCTION()。

PlayButton->OnClicked.AddDynamic(this, &UMyWidget::HandlePlayClicked);
PlayButton->SetColorAndOpacity(FLinearColor(1.f, 0.8f, 0.f, 1.f));
ScoreText->SetText(FText::Format(NSLOCTEXT("HUD", "ScoreFmt", "Score: {0}"), FText::AsNumber(Score)));
ScoreText->SetTextOverflowPolicy(ETextOverflowPolicy::Ellipsis);
PlayerAvatar->SetBrushFromSoftTexture(SoftAvatarTexture, /*bMatchSize=*/false);
UMaterialInstanceDynamic* AvatarMID = PlayerAvatar->GetDynamicMaterial();
AvatarMID->SetScalarParameterValue(TEXT("Opacity"), 0.5f);
HealthBar->SetPercent(CurrentHealth / MaxHealth);   // 0.0-1.0
HealthBar->SetFillColorAndOpacity(FLinearColor(0.f, 1.f, 0.f, 1.f));

UButton、UTextBlock、UImage、UProgressBar、UScrollBox、UWidgetSwitcher、UCheckBox、UEditableTextBox、USlider、UComboBoxString 和 UWidget 基类的方法及枚举表见 references/widget-types.md。

列表:UListView、UTileView、UTreeView

这三种控件都经过虚拟化处理——只有可见的条目控件会存在,而且会被重复利用。条目是 UObject*;条目控件派生自 UUserWidget 并实现 IUserObjectListEntry,重写 virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;(先调用 IUserObjectListEntry::NativeOnListItemObjectSet)。完整的条目控件示例和 UListView 方法表见 references/widget-types.md。

ItemList->ClearListItems();
for (const FMyItemInfo& Info : Items)
{
    UMyItemData* Data = NewObject<UMyItemData>(this);
    Data->DisplayName = Info.DisplayName;
    ItemList->AddItem(Data);
}
UMyItemData* Selected = ItemList->GetSelectedItem<UMyItemData>();

BP_OnItemClicked 是仅限 Blueprint 使用的私有委托。在 C++ 中,绑定原生事件 ItemList->OnItemClicked().AddUObject(...)(FSimpleListItemEvent,Components/ListViewBase.h:48;另有 OnItemSelectionChanged()),或者在 UListView 子类中重写 virtual void OnItemClickedInternal(UObject* Item) override;。在条目控件内,使用 GetListItem<UMyItemData>() 读取条目,并通过接口成员 IsListItemSelected() 查询选择状态(Blueprint/IUserListEntry.h:32)。

控件动画

PlayAnimation 及其变体会返回 FWidgetAnimationHandle(Animation/WidgetAnimationHandle.h);应保留该句柄,而不是序列播放器指针。有效句柄在动画结束后仍可能无法解析出任何状态,因此请检查 IsValid()。

// PlayAnimation(UWidgetAnimation*, StartAtTime=0, NumLoopsToPlay=1,
//               EUMGSequencePlayMode::Type=Forward, PlaybackSpeed=1, bRestoreState=false)
FWidgetAnimationHandle IntroHandle = PlayAnimation(IntroAnim);
IntroHandle.SetUserTag(TEXT("Intro"));
PlayAnimationForward(IntroAnim);                        // also PlayAnimationReverse
const float PausedAt = PauseAnimation(IntroAnim);
const bool bPlaying = IsAnimationPlaying(IntroAnim);
StopAnimation(IntroAnim);

输入模式和焦点

ue-input-system 负责输入模式和 Enhanced Input。最简正确形式:

FInputModeUIOnly UIMode;
UIMode.SetWidgetToFocus(Widget->TakeWidget());
UIMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
GetOwningPlayer()->SetInputMode(UIMode);
GetOwningPlayer()->SetShowMouseCursor(true);
// Restore gameplay:
GetOwningPlayer()->SetInputMode(FInputModeGameOnly());
GetOwningPlayer()->SetShowMouseCursor(false);

FInputModeGameAndUI 还提供 SetWidgetToFocus 和 SetLockMouseToViewportBehavior。应在添加控件的代码中(HUD、PlayerController、GameMode)设置输入模式,而不是在 NativeConstruct 中设置。启用 Common UI 时,不要调用 SetInputMode——应改为返回 FUIInputConfig。

通用 UI

启用 CommonUI 插件(其中包含 CommonUI 和 CommonInput 模块),将这两个模块添加到 Build.cs,并将视口客户端类设为 UCommonGameViewportClient——它会在游戏之前将输入重新路由到 UI 操作路由器(CommonGameViewportClient.h:21);UCommonInputSubsystem 的 FCommonInputPreprocessor 会检测游戏手柄、鼠标和触摸输入的变化,并由该子系统广播。设置详情、层架构和设置键见 references/common-ui-setup.md。

// MyMenuScreen.h
#pragma once
#include "CommonActivatableWidget.h"
#include "MyMenuScreen.generated.h"

class UCommonButtonBase;

UCLASS()
class MYGAME_API UMyMenuScreen : public UCommonActivatableWidget
{
    GENERATED_BODY()

protected:
    virtual void NativeOnActivated() override;
    virtual void NativeOnDeactivated() override;
    virtual UWidget* NativeGetDesiredFocusTarget() const override;
    virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
    virtual bool NativeOnHandleBackAction() override;   // Return true when handled

    UPROPERTY(meta=(BindWidget))
    TObjectPtr<UCommonButtonBase> CloseButton;

    void HandleCloseClicked();
};

// MyMenuScreen.cpp — #include "MyMenuScreen.h" and "CommonButtonBase.h"
void UMyMenuScreen::NativeOnActivated()
{
    Super::NativeOnActivated();     // Broadcasts OnActivated(), which activates this widget's tree node
    CloseButton->OnClicked().AddUObject(this, &UMyMenuScreen::HandleCloseClicked);
}

void UMyMenuScreen::NativeOnDeactivated()
{
    CloseButton->OnClicked().RemoveAll(this);
    Super::NativeOnDeactivated();   // Broadcasts OnDeactivated(); the router moves focus to the parent
}

UWidget* UMyMenuScreen::NativeGetDesiredFocusTarget() const { return CloseButton; }

TOptional<FUIInputConfig> UMyMenuScreen::GetDesiredInputConfig() const
{
    return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
}

bool UMyMenuScreen::NativeOnHandleBackAction() { DeactivateWidget(); return true; }
void UMyMenuScreen::HandleCloseClicked() { DeactivateWidget(); }

公共 API:ActivateWidget()、DeactivateWidget()、IsActivated()、GetDesiredFocusTarget()、ClearFocusRestorationTarget()、RequestRefreshFocus(),以及非动态的 OnActivated() / OnDeactivated() 委托。BP_GetDesiredFocusTarget 是 BlueprintImplementableEvent,NativeGetDesiredFocusTarget 会路由到它——在 C++ 中重载原生版本。可编辑默认值:bAutoActivate、bIsBackHandler、bIsBackActionDisplayedInActionBar、bIsModal、bAutoRestoreFocus、bSetVisibilityOnActivated/ActivatedVisibility、bSetVisibilityOnDeactivated/DeactivatedVisibility。ECommonInputMode 可设为 Menu(仅 UI)、Game(仅游戏)或 All。

容器

UCommonActivatableWidgetContainerBase(Widgets/CommonActivatableWidgetContainer.h)是基类;UCommonActivatableWidgetStack 显示最顶层的 Widget,并在其停用时重新激活下方的 Widget;UCommonActivatableWidgetQueue 一次显示一个 Widget,并在当前激活的 Widget 停用时切换到下一个。UCommonActivatableWidgetSwitcher 是基于索引的切换器,不是堆栈——此处不要使用它。

UPROPERTY(meta=(BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> MenuLayer;
// AddWidget<T> creates (or pools) the instance, adds it, and the container activates it.
UMyMenuScreen* Screen = MenuLayer->AddWidget<UMyMenuScreen>(UMyMenuScreen::StaticClass());
// Overload with an init lambda that runs after creation, before the widget is added:
MenuLayer->AddWidget<UMyMenuScreen>(UMyMenuScreen::StaticClass(),
    [](UMyMenuScreen& Instance) { Instance.SetIsEnabled(true); });

if (UCommonActivatableWidget* Active = MenuLayer->GetActiveWidget())
{
    Active->DeactivateWidget();         // Pops the stack
}
// RemoveWidget(UCommonActivatableWidget&) deactivates the active widget, or yanks an inactive one
MenuLayer->RemoveWidget(*Screen);
MenuLayer->ClearWidgets();              // GetNumWidgets() reports the current depth
按钮、输入类型、操作路由
// UCommonButtonBase::OnClicked() returns FCommonButtonEvent (DECLARE_EVENT, no params).
MyButton->OnClicked().AddUObject(this, &UMyMenuScreen::HandleCloseClicked);
MyButton->OnClicked().RemoveAll(this);
MyButton->SetIsSelected(true);
MyButton->SetIsInteractionEnabled(true);
UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(GetOwningLocalPlayer());
const ECommonInputType InputType = InputSubsystem->GetCurrentInputType();  // MouseAndKeyboard, Gamepad, Touch
InputSubsystem->OnInputMethodChangedNative.AddUObject(this, &UMyMenuScreen::HandleInputMethodChanged);

// UCommonUIActionRouterBase is the local-player subsystem that routes input through the activation tree.
if (UCommonUIActionRouterBase* Router = UCommonUIActionRouterBase::Get(*CloseButton))
{
    const ECommonInputMode Mode = Router->GetActiveInputMode();
}

// On a UCommonUserWidget subclass, with UPROPERTY(EditDefaultsOnly) TObjectPtr<UInputAction> BackInputAction:
FUIActionBindingHandle BackHandle = RegisterUIActionBinding(
    FBindUIActionArgs(BackInputAction, /*bShouldDisplayInActionBar=*/true,
        FSimpleDelegate::CreateUObject(this, &UMyMenuScreen::HandleCloseClicked)));

OnInputMethodChangedNative 是 DECLARE_EVENT_OneParam,因此 HandleInputMethodChanged(ECommonInputType) 不需要 UFUNCTION()。在 5.8 中,Enhanced Input 和 Common Input 已统一:启用 bEnableEnhancedInputSupport(UCommonInputSettings::IsEnhancedInputSupportEnabled())后,FBindUIActionArgs 接受 const UInputAction*,而 UCommonActivatableWidget::InputMapping / InputMappingPriority 会在激活时推送映射上下文。

MVVM

ModelViewViewModel 在 5.8 中为 Beta。视图模型是一个 UMVVMViewModelBase;FieldNotify 属性会生成字段描述符,而 UE_MVVM_SET_PROPERTY_VALUE 会在一次调用中执行比较、赋值和广播。

// MyGameViewModel.h
#pragma once
#include "MVVMViewModelBase.h"
#include "MyGameViewModel.generated.h"

UCLASS()
class MYGAME_API UMyGameViewModel : public UMVVMViewModelBase
{
    GENERATED_BODY()

public:
    void SetScore(int32 NewScore) { UE_MVVM_SET_PROPERTY_VALUE(Score, NewScore); }
    int32 GetScore() const { return Score; }

private:
    UPROPERTY(BlueprintReadOnly, FieldNotify, Getter, meta=(AllowPrivateAccess))
    int32 Score = 0;
};

// Binding at runtime - the UMVVMView extension on the user widget owns the generated bindings.
UMyGameViewModel* ViewModel = NewObject<UMyGameViewModel>(this);
if (UMVVMView* View = UMVVMSubsystem::GetViewFromUserWidget(MyWidget))
{
    View->SetViewModel(TEXT("ScoreViewModel"), ViewModel);   // Name from the Bindings panel
}

UE_MVVM_SET_PROPERTY_VALUE(MemberName, NewValue) 会展开为 SetPropertyValue(MemberName, NewValue, ThisClass::FFieldNotificationClassDescriptor::MemberName)。对于无法作为函数实参传递的值(位域),请使用 UE_MVVM_SET_PROPERTY_VALUE_INLINE;如果你自行赋值了成员,请使用 UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(MemberName);两者最终都会通过 INotifyFieldValueChanged 调用 BroadcastFieldValueChanged(UE::FieldNotification::FFieldId)。

UHT 会为每个 FieldNotify 属性和函数生成描述符。只有在 UCLASS(CustomFieldNotify) 类说明符下才手动声明字段(它不是 meta 键;见 Components/Widget.h:215),并在 FieldNotificationDeclaration.h(FieldNotification 模块;旧的 FieldNotification/ 路径自 5.3 起已弃用)中使用 UE_FIELD_NOTIFICATION_DECLARE_CLASS_DESCRIPTOR_BEGIN / UE_FIELD_NOTIFICATION_DECLARE_FIELD(Name, API_STRING) / UE_FIELD_NOTIFICATION_DECLARE_ENUM_FIELD(Name) / ..._END 系列宏,同时在 .cpp 中配对使用 UE_FIELD_NOTIFICATION_IMPLEMENTATION_BEGIN 和 UE_FIELD_NOTIFICATION_IMPLEMENTATION_END。UMVVMView 还提供 GetViewModel(FName)、SetViewModelByClass(...) 和 ExecuteViewModelBindings(FName);UMVVMSubsystem 是一个 UEngineSubsystem,而 GetViewFromUserWidget 是静态方法。

世界空间 UI

UWidgetComponent(Components/WidgetComponent.h,UMG 模块)会将 UUserWidget 渲染到关卡中的平面或圆柱体上。

// In the constructor of AMyCharacter (NameplateComponent is a UPROPERTY member):
NameplateComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("Nameplate"));
NameplateComponent->SetupAttachment(RootComponent);
NameplateComponent->SetWidgetSpace(EWidgetSpace::World);        // or EWidgetSpace::Screen
NameplateComponent->SetDrawSize(FVector2D(256.f, 64.f));
NameplateComponent->SetGeometryMode(EWidgetGeometryMode::Plane);// or Cylinder
NameplateComponent->SetTwoSided(false);
// At runtime:
NameplateComponent->SetWidget(NameplateWidget);
UUserWidget* Current = NameplateComponent->GetUserWidgetObject();
NameplateComponent->SetTickMode(ETickMode::Disabled);   // Then drive updates manually
NameplateComponent->RequestRedraw();

只有当 Widget 必须接收真实鼠标输入时才启用 bReceiveHardwareInput;否则请使用 UWidgetInteractionComponent 驱动交互。

Widget 树和辅助库

WidgetTree->ForEachWidget([](UWidget* Widget) { Widget->SetRenderOpacity(1.f); });
WidgetTree->ForEachWidgetAndDescendants([](UWidget* W) { W->SetIsEnabled(true); });
UButton* Found = WidgetTree->FindWidget<UButton>(TEXT("PlayButton"));
UMyEntryWidget* Built = WidgetTree->ConstructWidget<UMyEntryWidget>(EntryWidgetClass);

UWidgetBlueprintLibrary(Blueprint/WidgetBlueprintLibrary.h)包含画刷、拖放、FEventReply 和 FPaintContext 绘图辅助工具;USlateBlueprintLibrary(Blueprint/SlateBlueprintLibrary.h)用于在本地、绝对和视口空间之间转换。两者均列于 references/widget-types.md。

Slate

编辑器扩展以及 UMG 未公开的运行时 Widget 都可以使用 Slate。SWidget 是基类;自定义 Widget 通常以 SCompoundWidget 为父类。

自定义 Widget 在 SLATE_BEGIN_ARGS / SLATE_END_ARGS 之间声明参数——SLATE_ATTRIBUTE 用于可绑定到 lambda 的 TAttribute<T>,SLATE_ARGUMENT 用于普通值,SLATE_EVENT 用于委托——并在 Construct(const FArguments& InArgs) 中将子项构建到 ChildSlot。使用 CreateSP 绑定 Slate 委托,避免已销毁的 Widget 被调用。包含这三种参数的完整 SCompoundWidget 示例:references/slate-patterns.md。

SNew(WidgetType) 返回 TSharedRef<WidgetType>;SAssignNew(Var, WidgetType) 同样返回该类型,并将其存储到 TSharedPtr 中。FOnClicked 是 DECLARE_DELEGATE_RetVal(FReply, FOnClicked),因此点击处理程序必须返回 FReply。UWidget::GetCachedWidget() 可获取 UMG Widget 背后的 TSharedPtr<SWidget>;UUserWidget::TakeWidget() 会构建并返回 TSharedRef<SWidget>。

**标准 Slate Widget 的公开 TAttribute 成员正逐步改为私有。**不要直接读写这些成员;请使用弃用消息中指定的访问器——使用 Set<Name> 赋值,使用 Get<Name>Attribute() 读取 TSlateAttributeRef。在 5.8 中,受影响的属性包括 SCheckBox::PaddingOverride(SetPaddingOverride / GetPaddingOverrideAttribute)、SCheckBox::ForegroundColorOverride(SetForegroundColorOverride / GetForegroundColorOverrideAttribute)、SEditableText::Font(SetFont / GetFontAttribute)、SEditableText::ColorAndOpacity(SetColorAndOpacity / GetColorAndOpacityAttribute)以及 SMenuAnchor::Placement(SetMenuPlacement / GetPlacementAttribute)。

**Slate 与 UMG:**编辑器工具和需要最大控制力的场景使用 Slate;游戏运行时 UI、Blueprint 扩展性、动画和 BindWidget 使用 UMG。

Build.cs

PublicDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });
PrivateDependencyModuleNames.AddRange(new string[] {
    "CommonUI", "GameplayTags", // Activatable widgets, buttons, action router, FUIInputConfig; FBindUIActionArgs' FUIActionTag links GameplayTags
    "CommonInput", "EnhancedInput", // UCommonInputSubsystem, ECommonInputType/Mode; UInputAction UPROPERTYs
    "ModelViewViewModel",  // MVVM (Beta in 5.8)
    "FieldNotification" }); // Field-notification declaration macros

已弃用 — 请勿使用

请勿使用在 5.8 中使用来源
UUserWidget::RemoveFromViewport()RemoveFromParent()Blueprint/UserWidget.h:356 中的 UE_DEPRECATED(5.1)
PlayAnimation 返回的 UUMGSequencePlayer*FWidgetAnimationHandle / FWidgetAnimationStateAnimation/UMGSequencePlayer.h:29 中的 UE_DEPRECATED(5.6)
UUserWidget::ActiveSequencePlayers / StoppedSequencePlayersActiveAnimationsBlueprint/UserWidget.h:1491,1499 中的 UE_DEPRECATED(5.6)
UUserWidget::InitializeInputComponent()bAutomaticallyRegisterInputOnConstructionBlueprint/UserWidget.h:1713 中的 UE_DEPRECATED(5.7)
FWidgetStateBitfield 枚举状态访问器仅二进制状态和命名状态Binding/States/WidgetStateBitfield.h:35 中的 UE_DEPRECATED(5.5)
UListView::InitHorizontalEntrySpacing / InitVerticalEntrySpacingSetHorizontalEntrySpacing / SetVerticalEntrySpacingComponents/ListView.h:343,346 中的 UE_DEPRECATED(5.6)
USlateBlueprintLibrary::TransformScalarAbsoluteToLocal / TransformVectorAbsoluteToLocalScalar_LocalToAbsolute / Vector_LocalToAbsoluteBlueprint/SlateBlueprintLibrary.h:77,87 中的 UE_DEPRECATED(5.6)
UGameViewportSubsystem::Get(UWorld*)UGameViewportSubsystem::Get()Blueprint/GameViewportSubsystem.h:64 中的 UE_DEPRECATED(5.8)
直接访问 UButton::WidgetStyle / ColorAndOpacity / ClickMethod 字段SetStyle / SetColorAndOpacity / SetClickMethodComponents/Button.h:37,42,52 中的 UE_DEPRECATED(5.2)
UCommonUIActionRouterUCommonUIActionRouterBaseInput/CommonUIActionRouterBase.h:66 中的类名
RegisterLinkedPreprocessor(Widget, Processor, int32 DesiredIndex)接受 FInputPreprocessorRegistrationKey 的重载Input/CommonUIActionRouterBase.h:87 中的 UE_DEPRECATED(5.5)
FUIActionBinding::TryCreate(Widget, Args)TryCreate(Widget, Args, UserIndex)Input/UIActionBinding.h:35 中的 UE_DEPRECATED(5.6)
FOnRerouteTouchInputDelegate、UCommonGameViewportClient::OnRerouteTouchInput()FTouchRerouteDelegate、GetRerouteTouchRegistration() / GetRerouteTouchDelegate()CommonGameViewportClient.h:15,47 中的 UE_DEPRECATED(5.8)
#include "Input/CommonInputMode.h"(CommonUI)#include "CommonInputModeTypes.h"(CommonInput)Input/CommonInputMode.h 中的 UE_DEPRECATED_HEADER(5.1)
UMVVMViewModelBase::K2_BroadcastFieldValueChanged生成的属性设置器MVVMViewModelBase.h:71 中的 UE_DEPRECATED(5.3)

常见错误

**在 NativeConstruct 中绑定却不解绑:**每次重新添加小部件时,都会再次调用 NativeConstruct,因此未配对的 AddDynamic 会绑定两次。请在 NativeOnInitialized 中绑定一次(此时已绑定的小部件有效),或将 NativeConstruct 与 NativeDestruct 配对使用——切勿在 NativeTick 中绑定。

**使用 SetVisibility(Hidden) 关闭菜单:**小部件仍会占据布局空间,并且仍留在视口中(已设为根)。请调用 RemoveFromParent();如果小部件必须留在树中,则调用 SetVisibility(ESlateVisibility::Collapsed)。

**为玩家 UI 使用 CreateWidget(GetWorld(), ...):**请传入拥有该小部件的 APlayerController,这样小部件才会拥有本地玩家、支持分屏,并确保 GetOwningPlayer() 有效。

**在 UCommonButtonBase::OnClicked() 上使用 AddDynamic:**它返回的是 FCommonButtonEvent(通过 DECLARE_EVENT 声明,而不是动态委托)。请在 NativeOnActivated 中使用 AddUObject,并在 NativeOnDeactivated 中使用 RemoveAll(this)。

**跳过 Super::NativeOnActivated / Super::NativeOnDeactivated:**Super 调用会广播 OnActivated() / OnDeactivated(),小部件的激活树节点会监听这些调用(UIActionRouterTypes.cpp:1372),并推入/弹出 InputMapping;如果不调用它们,焦点、返回键处理和输入配置都会失效。

**调用 RemoveFromParent 后没有 UPROPERTY:**小部件会解除根引用,并可被垃圾回收。如果打算重新添加它,请用 UPROPERTY() TObjectPtr<> 持有它。

**未指定 Z 顺序:**以相同 Z 顺序添加的小部件,其绘制顺序未定义。请向 AddToViewport(ZOrder) 传入明确的值,并预留不同范围(例如 HUD 为 0-9,菜单为 10-19,弹窗为 20+)。Slate 会在绘制过程中对小部件执行 Tick(SWidget.cpp:1511),因此当小部件处于 Hidden 或 Collapsed 状态时,NativeTick 会停止——不要依赖 NativeTick 取消隐藏。

**在 Common UI 下调用 SetInputMode:**这会绕过激活树,并与路由器冲突。请改为从 GetDesiredInputConfig() 返回 FUIInputConfig。

相关技能

  • ue-input-system — Enhanced Input、输入映射上下文、FInputModeUIOnly / FInputModeGameAndUI / FInputModeGameOnly、输入优先级
  • ue-gameplay-framework — HUD/PlayerController 在何处创建并拥有小部件、分屏所有权
  • ue-cpp-foundations — UPROPERTY/UFUNCTION 说明符、TObjectPtr、GC 规则、子系统
  • ue-editor-tools — Slate 详细自定义、编辑器工具小部件、UToolMenus
  • ue-materials-rendering — UI 材质、由小部件驱动的 UMaterialInstanceDynamic 参数
  • ue-data-assets-tables — 为列表项和图标集提供数据支持的数据资产和数据表
  • ue-gameplay-abilities — 驱动生命值/资源条的属性变化委托
  • ue-blueprint-cpp-interop — 将 C++ 暴露给 Blueprint:UFUNCTION/UPROPERTY 元键、潜在操作和异步节点
  • ue-gameplay-cameras — 弹簧臂、视图目标、摄像机修改器、震动和 Gameplay Camera System

评论

支持 Markdown