关于 ue-editor-tools
面向 Unreal Engine 5.8 的编辑器扩展技能指南,适用于开发自定义工具和工作流的开发者。涵盖详情面板、编辑器工具 Widget、菜单工具栏、子系统、资产操作和数据验证。
主要功能
- 01编辑器模块配置
- 02详情面板自定义
- 03编辑器工具 Widget 与脚本操作
- 04菜单和工具栏扩展
- 05编辑器子系统与资产工具
适用场景
- 01添加自定义属性编辑界面
- 02制作编辑器内工具面板
- 03扩展 Content Browser 右键操作
- 04创建菜单命令或编辑器子系统
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-editor-tools安装后在 Claude Code 中输入/ue-editor-tools确认已加载。
附带文件references/detail-customization-patterns.mdreferences/editor-module-setup.md
详细介绍
UE 编辑器工具
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;较旧的形式列在“已弃用 — 请勿使用”下。
编辑器扩展位于 "Type": "Editor" 的模块中,并依赖 UnrealEd、Slate、SlateCore、PropertyEditor(详情面板)、ToolMenus(菜单和工具栏)、EditorSubsystem(UEditorSubsystem)、Blutility(编辑器实用工具控件和脚本化操作)、AssetTools 和 AssetDefinition(内容浏览器资源类型)。这些模块都不能从 Runtime 模块中无条件引用(引擎 Runtime 模块仅在 if (Target.bBuildEditor) 和 #if WITH_EDITOR 下添加它们,例如 AIModule.Build.cs:31-34)。
上下文
如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果文件不存在,也请继续。
根据请求和代码库确定所属领域。只有在两种合理解读会产生不同代码时才提问。
| 请求涉及…… | 前往 |
|---|---|
| 创建编辑器模块、Build.cs、注册生命周期 | 编辑器模块设置 |
| 为类或结构体自定义属性面板 | 详情自定义 |
| 在编辑器中运行的 UMG 面板、右键资源/Actor 操作 | 编辑器实用工具控件和脚本化操作 |
| 主菜单、工具栏、上下文菜单项、命令 | 菜单和工具栏 |
编辑器生命周期单例、GEditor->GetEditorSubsystem<T>() | 编辑器子系统 |
| 撤销/重做、标记对象为脏、响应编辑器事件 | 事务和编辑器委托 |
| 带有自定义交互或工具的视口模式 | 编辑器模式 |
| 内容浏览器类型颜色、类别、工厂、缩略图 | 资源类型、工厂和缩略图 |
| 带标签页的资源编辑器窗口 | 资源编辑器工具包 |
| 在保存时或 CI 中验证资源 | 数据验证 |
编辑器模块设置
.uproject(或 .uplugin)模块条目——详情自定义和 UToolMenus::RegisterStartupCallback 菜单使用 Default 即可(CommonUIEditor 在 Default 阶段注册其详情自定义);当 StartupModule 本身需要访问 GEditor 或其他已初始化的引擎状态时,使用 PostEngineInit:
{ "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" }
// MyGameEditor.Build.cs
PrivateDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "UnrealEd",
"Slate", "SlateCore", "InputCore",
"PropertyEditor", // IDetailCustomization, IPropertyTypeCustomization
"EditorSubsystem", // UEditorSubsystem
"Blutility", // UEditorUtilityWidget, UAssetActionUtility
"UMG", "UMGEditor", // EditorUtilityWidget(Blueprint).h include UMG headers; Blutility links them privately
"ToolMenus", // UToolMenus
"AssetTools", // IAssetTools
"AssetDefinition", // UAssetDefinition
"MyGame" // your runtime module
});
声明模块类,定义两个生命周期函数,然后将 IMPLEMENT_MODULE 放在最后:
// MyGameEditor.h
#pragma once
#include "Modules/ModuleManager.h"
class FMyGameEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterMenus();
static void OpenMyPanel();
};
// MyGameEditorModule.cpp — includes MyDataAsset.h, MyRange.h, the two customization
// headers, PropertyEditorModule.h and ToolMenus.h
void FMyGameEditorModule::StartupModule()
{
FPropertyEditorModule& PropertyModule =
FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout(
UMyDataAsset::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FMyDataAssetCustomization::MakeInstance),
FRegisterCustomClassLayoutParams());
PropertyModule.RegisterCustomPropertyTypeLayout(
FMyRange::StaticStruct()->GetFName(),
FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMyRangeCustomization::MakeInstance));
PropertyModule.NotifyCustomizationModuleChanged();
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FMyGameEditorModule::RegisterMenus));
}
void FMyGameEditorModule::ShutdownModule()
{
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FPropertyEditorModule& PropertyModule =
FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.UnregisterCustomClassLayout(UMyDataAsset::StaticClass()->GetFName());
PropertyModule.UnregisterCustomPropertyTypeLayout(FMyRange::StaticStruct()->GetFName());
PropertyModule.NotifyCustomizationModuleChanged();
}
}
IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)
RegisterMenus 和 OpenMyPanel 的定义见菜单和工具栏。FRegisterCustomClassLayoutParams 包含 TOptional<int32> OptionalOrder;当两个模块自定义同一个类且顺序很重要时设置该值,否则传入默认构造的结构体。
目录布局、WITH_EDITOR 保护、工厂、缩略图、工具包和完整的模式样板代码:editor-module-setup.md。
详细信息自定义
IDetailCustomization 会替换 UClass 的整个详细信息面板布局。IPropertyTypeCustomization 会替换 USTRUCT 或实例化对象属性所在的行。两者都在 StartupModule 中注册,如上所示。
// MyDataAssetCustomization.h
#pragma once
#include "IDetailCustomization.h"
#include "Templates/SharedPointer.h"
class FMyDataAssetCustomization : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
virtual void CustomizeDetails(const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder) override;
private:
TWeakPtr<IDetailLayoutBuilder> WeakBuilder;
};
// MyDataAssetCustomization.cpp
void FMyDataAssetCustomization::CustomizeDetails(const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder)
{
WeakBuilder = DetailBuilder; // store it before calling ForceRefreshDetails() later
CustomizeDetails(*DetailBuilder);
}
单参数重载负责布局工作:EditCategory、GetProperty、AddProperty、AddCustomRow、HideProperty、GetObjectsBeingCustomized——完整函数体见下方参考资料。
USTRUCT 对应的实现 FMyRangeCustomization : public IPropertyTypeCustomization,会重载 CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) 和 CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils)。
关键接口(均位于 Engine/Source/Editor/PropertyEditor/Public):
| 类型 | 头文件 | 调用方法 |
|---|---|---|
IDetailLayoutBuilder | DetailLayoutBuilder.h | EditCategory、HideCategory、GetProperty、HideProperty、AddPropertyToCategory、GetObjectsBeingCustomized、GetSelectedObjects、ForceRefreshDetails、RegisterInstancedCustomPropertyTypeLayout |
IDetailCategoryBuilder | DetailCategoryBuilder.h | AddProperty、AddCustomRow、AddCustomBuilder、AddGroup、AddExternalObjects、SetSortOrder、InitiallyCollapsed(IDetailChildrenBuilder.h 中的 IDetailChildrenBuilder 提供相同的五种添加方法,以及 CustomizeChildren 中的 AddExternalStructure) |
FDetailWidgetRow | DetailWidgetRow.h | NameContent()、ValueContent()、WholeRowContent()、ExtensionContent()、FilterString、Visibility、IsEnabled |
IPropertyHandle | PropertyHandle.h | GetValue/SetValue 重载、GetNumChildren、GetChildHandle、SetOnPropertyValueChanged、SetOnChildPropertyValueChanged、CreatePropertyNameWidget、CreatePropertyValueWidget、EnumerateRawData、NotifyPreChange、NotifyPostChange |
IDetailPropertyRow | IDetailPropertyRow.h | DisplayName、ToolTip、EditCondition、IsEnabled、Visibility、GetDefaultWidgets、CustomWidget |
PropertyCustomizationHelpers | PropertyCustomizationHelpers.h | MakeAddButton、MakeRemoveButton、MakeClearButton、MakeBrowseButton、MakeUseSelectedButton、MakePropertyComboBox、SObjectPropertyEntryBox、SClassPropertyEntryBox |
GetValue/SetValue 返回 FPropertyAccess::Result(Success、Fail、MultipleValues)。NotifyPostChange 接受 EPropertyChangeType::Type,例如 EPropertyChangeType::ValueSet。ECategoryPriority::Type 按从上到下的顺序排序:Variable、Transform、Important、TypeSpecific、Default、Uncommon。
完整的行布局模式、CustomizeHeader/CustomizeChildren 函数体、SObjectPropertyEntryBox、逐对象值枚举和字体说明:detail-customization-patterns.md。
编辑器实用控件和脚本化操作
UEditorUtilityWidget(EditorUtilityWidget.h,模块 Blutility)是一个在编辑器中运行的 UUserWidget。在 C++ 中创建子类以公开辅助函数,然后在 Blueprint 子类中构建界面。
// MyEditorUtilityWidget.h
#pragma once
#include "EditorUtilityWidget.h"
#include "MyEditorUtilityWidget.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyEditorUtilityWidget : public UEditorUtilityWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "My Tools")
void PrefixSelectedAssets(const FString& Prefix);
};
// MyEditorUtilityWidget.cpp
void UMyEditorUtilityWidget::PrefixSelectedAssets(const FString& Prefix)
{
UEditorAssetSubsystem* AssetSubsystem = GEditor->GetEditorSubsystem<UEditorAssetSubsystem>();
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
const FString Source = Asset->GetOutermost()->GetName();
AssetSubsystem->RenameAsset(
Source, FPaths::GetPath(Source) / (Prefix + TEXT("_") + Asset->GetName()));
}
}
UEditorUtilityLibrary(EditorUtilityLibrary.h,Blutility)还提供 GetSelectedAssetData()、GetSelectedAssetsOfClass(UClass*)、GetSelectionSet()(关卡 Actor)和 SyncBrowserToFolders(const TArray<FString>&)。
通过 UEditorUtilitySubsystem(EditorUtilitySubsystem.h)从代码中打开控件 Blueprint:
UEditorUtilitySubsystem 调用 | 效果 |
|---|---|
SpawnAndRegisterTab(UEditorUtilityWidgetBlueprint*) | 注册并打开选项卡;返回 UEditorUtilityWidget* |
SpawnAndRegisterTabAndGetID(UEditorUtilityWidgetBlueprint*, FName& OutTabID) / SpawnAndRegisterTabWithId(UEditorUtilityWidgetBlueprint*, FName InTabID) | 同上,写回或提供选项卡 ID |
RegisterTabAndGetID(UEditorUtilityWidgetBlueprint*, FName& OutTabID) | 注册选项卡生成器,但不打开选项卡 |
TryRun(UObject* Asset) | 运行派生自 UEditorUtilityObject 的资源(调用其 Run) |
脚本化操作:UAssetActionUtility 子类中的任何 UFUNCTION(CallInEditor) 都会显示在 Content Browser 右键菜单中;UActorActionUtility 对关卡 Actor 执行相同操作。两者都派生自 UEditorUtilityObject。使用 SupportedClasses 数组(TArray<TSoftClassPtr<UObject>>,可在 Class Defaults 中编辑)进行筛选;使用 GetSupportedClasses() 读取。
// MyAssetActionUtility.h
#pragma once
#include "AssetActionUtility.h"
#include "MyAssetActionUtility.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyAssetActionUtility : public UAssetActionUtility
{
GENERATED_BODY()
public:
UFUNCTION(CallInEditor, Category = "My Tools")
void SetTextureCompressionToUI();
};
// MyAssetActionUtility.cpp
void UMyAssetActionUtility::SetTextureCompressionToUI()
{
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
if (UTexture2D* Texture = Cast<UTexture2D>(Asset))
{
Texture->CompressionSettings = TC_EditorIcon;
Texture->MarkPackageDirty();
Texture->PostEditChange();
}
}
}
编辑器通过资产注册表标签构建这些菜单,不会加载实用工具资源;这些标签由 FAssetActionUtilityPrototype(EditorUtilityAssetPrototype.h)封装。请将筛选条件保存在 CDO 的 SupportedClasses 中,而不是在运行时计算。
菜单和工具栏
UToolMenus(模块 ToolMenus)负责管理所有编辑器菜单和工具栏。请从启动回调中注册,以确保 Slate 和要扩展的菜单已就绪。
// MyGameEditorModule.cpp (continued) — ToolMenuSection.h, ToolMenuEntry.h,
// Styling/AppStyle.h, EditorUtilitySubsystem.h, EditorUtilityWidgetBlueprint.h
void FMyGameEditorModule::OpenMyPanel()
{
if (UEditorUtilityWidgetBlueprint* WidgetBP = LoadObject<UEditorUtilityWidgetBlueprint>(
nullptr, TEXT("/Game/EditorWidgets/BP_MyTool.BP_MyTool")))
{
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()->SpawnAndRegisterTab(WidgetBP);
}
}
void FMyGameEditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* WindowMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
FToolMenuSection& Section = WindowMenu->FindOrAddSection("MyGame");
Section.Label = FText::FromString("My Game");
Section.AddMenuEntry(
"OpenMyPanel",
FText::FromString("My Panel"),
FText::FromString("Open the My Game tool panel"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings"),
FUIAction(FExecuteAction::CreateStatic(&FMyGameEditorModule::OpenMyPanel)));
UToolMenu* PlayToolBar =
UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
PlayToolBar->FindOrAddSection("MyTools").AddEntry(
FToolMenuEntry::InitToolBarButton(
"MyToolButton",
FUIAction(FExecuteAction::CreateStatic(&FMyGameEditorModule::OpenMyPanel)),
FText::FromString("My Tool"),
FText::FromString("Run the My Game tool"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Toolbar.Settings")));
}
| 调用 | 用途 |
|---|---|
UToolMenus::Get() / UToolMenus::TryGet() | 单例;TryGet 不会强制加载模块 |
RegisterMenu(FName Name, FName Parent = NAME_None, EMultiBoxType Type = EMultiBoxType::Menu, bool bWarnIfAlreadyRegistered = true) | 声明由你拥有的菜单 |
ExtendMenu(FName) | 向其他人拥有的菜单添加内容;如果菜单尚未注册,则会排队等待扩展 |
FindMenu(FName) | 查找已注册的菜单;不存在时返回 nullptr |
UToolMenu::FindOrAddSection(FName) / AddSection(FName, Label, FToolMenuInsert) | 获取或创建区段 |
FToolMenuSection::AddMenuEntry(FName, Label, ToolTip, FSlateIcon, FToolUIActionChoice, EUserInterfaceActionType, FName TutorialHighlight, TOptional<FText> InputBindingOverride) | 菜单项 |
FToolMenuSection::AddSubMenu / AddSeparator / AddEntry | 子菜单、分隔符、预构建条目 |
FToolMenuEntry::InitToolBarButton / InitComboButton / InitWidget / InitMenuEntry | 构建要传给 AddEntry 的条目 |
UToolMenus::RegisterStartupCallback(const FSimpleMulticastDelegate::FDelegate&) | 延迟注册,直到菜单就绪 |
UToolMenus::UnRegisterStartupCallback(this) + UToolMenus::UnregisterOwner(this) | 必须在 ShutdownModule 中调用 |
FToolMenuOwnerScoped 会将其作用域内创建的所有内容标记为归属该所有者,以便 UnregisterOwner 将其全部移除。常用菜单名称:LevelEditor.MainMenu.Window、LevelEditor.MainMenu.Tools、LevelEditor.LevelEditorToolBar.PlayToolBar、LevelEditor.LevelEditorToolBar.User、LevelEditor.ActorContextMenu、ContentBrowser.AssetContextMenu。图标使用 FSlateIcon(FAppStyle::GetAppStyleSetName(), "StyleName")。
键盘快捷键和可复用命令使用 TCommands 和 UI_COMMAND;参见 editor-module-setup.md。
编辑器子系统
所属模块加载后,会自动实例化一次 UEditorSubsystem(模块 EditorSubsystem,头文件 EditorSubsystem.h),无需调用注册函数。使用 GEditor->GetEditorSubsystem<T>() 获取它。
// MyEditorSubsystem.h
#pragma once
#include "EditorSubsystem.h"
#include "MyEditorSubsystem.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyEditorSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
private:
void OnMapOpened(const FString& Filename, bool bAsTemplate);
};
// MyEditorSubsystem.cpp
void UMyEditorSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
FEditorDelegates::OnMapOpened.AddUObject(this, &UMyEditorSubsystem::OnMapOpened);
}
void UMyEditorSubsystem::Deinitialize()
{
FEditorDelegates::OnMapOpened.RemoveAll(this);
Super::Deinitialize();
}
void UMyEditorSubsystem::OnMapOpened(const FString& Filename, bool bAsTemplate)
{
UE_LOG(LogMyGame, Display, TEXT("Opened map %s"), *Filename);
}
UEditorSubsystem 派生自 UDynamicSubsystem;Initialize(FSubsystemCollectionBase&)、Deinitialize() 和 ShouldCreateSubsystem(UObject* Outer) const 来自 USubsystem,因此务必调用 Super::。编写自己的子系统之前,值得先了解以下引擎编辑器子系统:
| 子系统 | 模块 | 用途 |
|---|---|---|
UEditorActorSubsystem | UnrealEd | GetAllLevelActors、GetSelectedLevelActors、SetSelectedLevelActors、SpawnActorFromClass、DestroyActor |
UEditorAssetSubsystem | UnrealEd | LoadAsset、RenameAsset、DuplicateAsset、DeleteAsset、SaveLoadedAsset、ListAssets |
UAssetEditorSubsystem | UnrealEd | OpenEditorForAsset、FindEditorForAsset、CloseAllEditorsForAsset、GetEditorModeInfoOrderedByPriority |
UEditorUtilitySubsystem | Blutility | SpawnAndRegisterTab、RegisterTabAndGetID、TryRun |
ULevelEditorSubsystem | LevelEditor | 打开、保存和新建关卡的操作 |
规范的子系统类型表(游戏实例、世界、本地玩家、引擎、编辑器)位于 ue-cpp-foundations。
事务与编辑器委托
对 UObject 的每项编辑器修改都必须用事务包裹,并在修改前调用 Modify(),否则撤销时会恢复过期数据:
#include "ScopedTransaction.h"
#include "Subsystems/EditorActorSubsystem.h"
void UMyEditorSubsystem::OffsetSelection(const FVector& Delta)
{
const FScopedTransaction Transaction(
NSLOCTEXT("MyGameEditor", "OffsetSelection", "Offset Selection"));
UEditorActorSubsystem* ActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
for (AActor* Actor : ActorSubsystem->GetSelectedLevelActors())
{
Actor->Modify();
Actor->SetActorLocation(Actor->GetActorLocation() + Delta);
}
}
FScopedTransaction(ScopedTransaction.h,UnrealEd)还提供 Cancel() 和 IsOutstanding()。直接写入资产属性后,调用 MarkPackageDirty() 和 PostEditChange()。
| 委托 | 签名 |
|---|---|
FEditorDelegates::PostUndoRedo | FSimpleMulticastDelegate |
FEditorDelegates::OnMapOpened | (const FString& Filename, bool bAsTemplate) |
FEditorDelegates::PreBeginPIE / PostPIEStarted / EndPIE | (const bool bIsSimulating) |
FEditorDelegates::OnAssetPostImport | (UFactory*, UObject*) |
FEditorDelegates::OnNewActorsPlaced | (UObject*, const TArray<AActor*>&) |
FEditorDelegates::OnPackageDeleted / OnAssetsPreDelete | (UPackage*) / (const TArray<UObject*>&) |
FCoreUObjectDelegates::OnObjectPropertyChanged / OnPreObjectPropertyChanged | (UObject*, FPropertyChangedEvent&) / (UObject*, const FEditPropertyChain&) |
FCoreUObjectDelegates::OnObjectModified | (UObject*) |
编辑器模式
UEdMode(Tools/UEdMode.h,UnrealEd)是模式基类。所有 UEdMode 子类都会在所有模块加载阶段完成后,由 UAssetEditorSubsystem::RegisterEditorModes 自动发现——在构造函数中填写 Info,无需调用注册表。
// MyEditorMode.h
#pragma once
#include "Tools/UEdMode.h"
#include "MyEditorMode.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyEditorMode : public UEdMode
{
GENERATED_BODY()
public:
static const FEditorModeID EM_MyEditorMode;
UMyEditorMode();
virtual void Enter() override;
virtual void Exit() override;
virtual void ModeTick(float DeltaTime) override;
virtual void CreateToolkit() override;
virtual bool UsesToolkits() const override;
};
- 在构造函数中设置
Info = FEditorModeInfo(EM_MyEditorMode, DisplayName, FSlateIcon(...), /*InVisibility=*/true);Info是UEdMode的受保护成员。Enter()和Exit()必须调用Super::。 - 交互工具:在
Enter()中调用RegisterTool(TSharedPtr<FUICommandInfo> UICommand, FString ToolIdentifier, UInteractiveToolBuilder* Builder, EToolsContextScope ToolScope = EToolsContextScope::Default)。GetInteractiveToolsContext(EToolsContextScope)返回UEditorInteractiveToolsContext;模式自身的上下文对象是UEdModeInteractiveToolsContext。 - 视口输入(
MouseMove、InputKey、HandleClick、InputDelta、Tick)通过实现ILegacyEdModeViewportInterface(Tools/LegacyEdModeInterfaces.h)提供。Render和DrawHUD通过ILegacyEdModeWidgetInterface提供;UBaseLegacyWidgetEdMode实现了这两个接口,并将调用转发给CreateWidgetHelper()返回的FLegacyEdModeWidgetHelper。 - 使用
GLevelEditorModeTools().ActivateMode(UMyEditorMode::EM_MyEditorMode)激活;使用IsModeActive/GetActiveScriptableMode查询;使用DeactivateMode退出。 FEdMode(EdMode.h)是旧版共享指针模式;它继承自FLegacyEdModeWidgetHelper,并通过FEditorModeRegistry::Get().RegisterMode<T>(...)/UnregisterMode(...)注册。新模式请使用UEdMode编写。
带工具包的完整模式实现:editor-module-setup.md。
资产类型、工厂和缩略图
UAssetDefinition(模块 AssetDefinition,头文件 AssetDefinition.h)是 5.8 中与内容浏览器集成的入口点。继承 UAssetDefinitionDefault(AssetDefinitionDefault.h,UnrealEd),这样 OpenAssets 就会默认打开通用的 FSimpleAssetEditor(AssetDefinitionDefault.cpp:16-25)。系统会通过定义类的 CDO 自动发现定义,无需注册调用。
重写 FText GetAssetDisplayName() const、FLinearColor GetAssetColor() const、TSoftClassPtr<UObject> GetAssetClass() const 和 TConstArrayView<FAssetCategoryPath> GetAssetCategories() const;仅当默认工具包路由不符合需求时,才重写 EAssetCommandResult OpenAssets(const FAssetOpenArgs& OpenArgs) const。
EAssetCategoryPaths 提供 Basic、Animation、Audio、Blueprint、Cinematics、Data、Foliage、FX、Gameplay、AI 等分类;使用 FAssetCategoryPath(EAssetCategoryPaths::Gameplay, INVTEXT("My Game")) 嵌套分类。此类型的上下文菜单项通过 UToolMenus 对 ContentBrowser.AssetContextMenu 的扩展添加,而不是通过定义类添加。
FAssetTypeActions_Base(AssetTypeActions_Base.h,模块 AssetTools)以及 IAssetTools::RegisterAssetTypeActions / UnregisterAssetTypeActions 仍可编译,但 IAssetTypeActions 的成员正逐步弃用,转而采用定义系统。新类型请使用 UAssetDefinition。
- 工厂:编辑器模块中的
UFactory子类在构造函数中设置SupportedClass、bCreateNew、bEditAfterNew,并重写FactoryCreateNew(UClass*, UObject*, FName, EObjectFlags, UObject*, FFeedbackContext*)。系统会自动发现,无需注册。 - 缩略图:继承
UThumbnailRenderer,重写Draw(UObject*, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget*, FCanvas*, bool bAdditionalViewFamily),然后在StartupModule中调用UThumbnailManager::Get().RegisterCustomRenderer(UMyDataAsset::StaticClass(), UMyThumbnailRenderer::StaticClass()),并在ShutdownModule中调用UnregisterCustomRenderer(...)。 - 查询:构建
FARFilter(AssetRegistry/ARFilter.h),设置ClassPaths、PackagePaths、bRecursiveClasses、bRecursivePaths,然后将其传递给IAssetRegistry::GetAssets(const FARFilter&, TArray<FAssetData>&, bool bSkipARFilteredAssets = true)。
定义、工厂和缩略图的完整实现:editor-module-setup.md。
资产编辑器工具包
FAssetEditorToolkit(Toolkits/AssetEditorToolkit.h)用于承载带标签页的编辑器窗口。GetToolkitFName、GetBaseToolkitName 和 GetWorldCentricTabPrefix 是纯虚函数;你的初始化函数需使用 FTabManager::NewLayout 构建布局,并调用 InitAssetEditor(Mode, InitToolkitHost, AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit)。
使用 IAssetEditorInstance* Instance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Asset, /*bFocusIfOpen=*/false); 查找或聚焦已打开的编辑器,然后调用 Instance->FocusWindow(Asset) 或 Instance->CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed)。
数据验证
DataValidation 插件中包含 UEditorValidatorBase(默认启用;模块 DataValidation,LoadingPhase: PreDefault)。系统会通过验证器的 CDO 自动发现验证器;重写 CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const 和 ValidateLoadedAsset_Implementation(const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context)。使用 UnrealEditor-Cmd <Project>.uproject -run=DataValidation 无头运行。
已弃用 — 请勿使用
| 不要使用 | 在 5.8 中使用 | 来源 |
|---|---|---|
FEditorStyle::Get()、FEditorStyle::GetBrush/GetFontStyle | FAppStyle::Get()、FAppStyle::GetBrush/GetFontStyle | Editor/EditorStyle/Public/EditorStyleSet.h 中的 UE_DEPRECATED(5.1) |
IPropertyHandle::CreatePropertyNameWidget(Name, ToolTip, bDisplayResetToDefault, ...) | CreatePropertyNameWidget(NameOverride, ToolTipOverride) | PropertyEditor/Public/PropertyHandle.h:641 中的 UE_DEPRECATED(5.0) |
UAssetActionUtility::GetSupportedClass() / UActorActionUtility::GetSupportedClass() | SupportedClasses 数组 + GetSupportedClasses() | Blutility/Classes/AssetActionUtility.h:69、ActorActionUtility.h:24 中的 UE_DEPRECATED(5.2) |
IAssetEditorInstance::CloseWindow()(无参数) | CloseWindow(EAssetEditorCloseReason) | UnrealEd/Public/Subsystems/AssetEditorSubsystem.h:62 中的 UE_DEPRECATED(5.3) |
FAssetEditorToolkit::OnRequestClose()(无参数) | OnRequestClose(EAssetEditorCloseReason) | UnrealEd/Public/Toolkits/AssetEditorToolkit.h:435 中的 UE_DEPRECATED(5.3) |
UEditorValidatorBase::CanValidateAsset_Implementation(UObject*)、ValidateLoadedAsset_Implementation(UObject*, TArray<FText>&) | 接受 (const FAssetData&, UObject*, FDataValidationContext&) 的重载 | DataValidation/Public/EditorValidatorBase.h:125,130 中的 UE_DEPRECATED("5.4") |
IDetailLayoutBuilder::GetDetailsView() | GetDetailsViewSharedPtr() | PropertyEditor/Public/DetailLayoutBuilder.h:63,74 中的 UE_DEPRECATED(5.5) |
IDetailTreeNode::GetNodeDetailsView() | GetNodeDetailsViewSharedPtr() | PropertyEditor/Public/IDetailTreeNode.h:128 中的 UE_DEPRECATED(5.5) |
IAssetTypeActions::GetThumbnailOverlay | UAssetDefinition + GetThumbnailActionOverlay | Developer/AssetTools/Public/IAssetTypeActions.h:122 中的 UE_DEPRECATED(5.5) |
FEditorModeRegistry::GetModeInfo 迭代辅助函数、OnRegisteredModesChanged | UAssetEditorSubsystem::GetEditorModeInfoOrderedByPriority、FindEditorModeInfo、OnEditorModesChanged | UnrealEd/Public/EditorModeRegistry.h:98,104,141 中的 UE_DEPRECATED(4.26) |
UEdModeInteractiveToolsContext::UsesDragTools() / OnRegisterViewportInteractions() | UsesViewportInteractions() / OnBuildViewportInteractions()(实验性) | UnrealEd/Public/Tools/EdModeInteractiveToolsContext.h:427,441 中的 UE_DEPRECATED(5.8) |
FEditorModeTools::GetOverrideCursorVisibility(bool&, bool&, bool)(按值传递的 bSoftwareCursorVisible) | GetCursorVisibilityOverride(bool&, bool&, bool&) | UnrealEd/Public/EditorModeManager.h:321 中的 UE_DEPRECATED(5.8) |
常见错误
在 Runtime 模块中包含编辑器头文件: UnrealEd、PropertyEditor、Blutility 和 ToolMenus 仅供编辑器使用,不会链接到打包后的构建中,因此,无条件依赖它们的 Runtime 模块无法打包(UBT 会抛出 BuildException,UnrealEd.Build.cs:12-14)。将代码放在 MyGameEditor 中;仅对 PostEditChangeProperty 等运行时类钩子使用 #if WITH_EDITOR。
在类定义之前使用 IMPLEMENT_MODULE: 该宏必须放在 FMyGameEditorModule 声明和两个函数定义之后——将它放在 .cpp 文件末尾。
注册后未取消注册: 每个 RegisterCustomClassLayout、RegisterCustomPropertyTypeLayout、RegisterCustomRenderer 和 RegisterStartupCallback 都需要在 ShutdownModule 中有对应的取消注册操作,并使用 FModuleManager::Get().IsModuleLoaded(...) 进行保护。缺少对应操作会导致 Live Coding 重新加载时崩溃。
修改 UObject 时未使用事务: 写入前始终使用 FScopedTransaction 和 Object->Modify()。
对 UEdMode 调用 FEditorModeRegistry::RegisterMode: UEdMode 子类会根据其 CDO 自动注册;该注册表仅用于 FEdMode。
在 Slate lambda 中捕获原始 this: 捕获 TWeakPtr/TWeakObjectPtr,并在使用前调用 pin——细节面板小组件的生命周期会超过定制对象。
每次值变化都调用 ForceRefreshDetails(): 这会销毁并重建布局。仅在行结构变化时调用;使用 TAttribute 和 _Lambda 绑定来驱动值。
在 LoadingPhase: Default 时于 StartupModule 中访问 GEditor: Default 阶段加载的项目模块会早于编辑器引擎加载;请通过 UToolMenus::RegisterStartupCallback / FCoreDelegates::GetOnPostEngineInit() 延迟执行(OnPostEngineInit 成员已标记为 UE_DEPRECATED(5.8),CoreDelegates.h:240),或在 PostEngineInit 阶段加载。
相关技能
ue-module-build-system—.Build.cs语法、模块类型、公共与私有依赖、插件描述符ue-cpp-foundations—UCLASS/UPROPERTY/UFUNCTION说明符、规范的子系统类型表、UObject生命周期ue-ui-umg-slate— Slate 小组件基础知识(SNew、TAttribute、FReply、复合小组件),每个自定义行都会用到ue-data-assets-tables— 此类编辑器和定义所封装的UDataAsset、UPrimaryDataAsset和UDataTable类型ue-testing-debugging—UE_LOG分类和详细程度、编辑器代码自动化测试、Insights 性能分析ue-actor-component-architecture— 需要定制其细节面板的 Actor 和组件类ue-project-context— 编写和使用.agents/ue-project-context.mdue-blueprint-cpp-interop— 将 C++ 暴露给 Blueprint:UFUNCTION/UPROPERTY 元键、潜伏操作和异步节点