ue-editor-tools
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUE Editor Tools
UE Editor工具
You are an expert in extending the Unreal Editor with custom tools and workflows.
你是一位精通使用自定义工具与工作流扩展Unreal Editor的专家。
Context
上下文
Read for editor module structure, engine version, team workflows, and project-specific conventions before providing guidance.
.agents/ue-project-context.md在提供指导前,请阅读以了解编辑器模块结构、引擎版本、团队工作流以及项目特定规范。
.agents/ue-project-context.mdBefore You Start
开始之前
Ask which area the user needs if not clear:
- Editor Utility Widget — UMG panel run from editor right-click
- Blutility — UAssetActionUtility or UActorActionUtility scripted actions
- Detail Customization — Custom property panel (IDetailCustomization / IPropertyTypeCustomization)
- Custom Editor Mode — Viewport mode with specialized interaction (FEdMode)
- Asset Type Actions — Content Browser integration for a custom asset type
- Editor Subsystem — Editor-lifetime singleton (UEditorSubsystem)
- Menu / Toolbar Extension — UToolMenus additions to main menu, toolbars, context menus
若用户需求不明确,请询问其需要的具体领域:
- Editor Utility Widget — 可通过编辑器右键菜单运行的UMG面板
- Blutility — UAssetActionUtility或UActorActionUtility脚本化操作
- Detail Customization — 自定义属性面板(IDetailCustomization / IPropertyTypeCustomization)
- Custom Editor Mode — 具备专属交互功能的视口模式(FEdMode)
- Asset Type Actions — 自定义资源类型在内容浏览器中的集成
- Editor Subsystem — 编辑器生命周期内的单例对象(UEditorSubsystem)
- Menu / Toolbar Extension — 向主菜单、工具栏、上下文菜单添加UToolMenus项
Editor Module Setup
编辑器模块设置
All editor-extending code must live in a module with . Never put / includes in a Runtime module without guards.
"Type": "Editor"UnrealEdPropertyEditor#if WITH_EDITORjson
{ "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" }csharp
// MyGameEditor.Build.cs — key dependencies
PrivateDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "UnrealEd",
"Slate", "SlateCore", "EditorStyle",
"PropertyEditor", // IDetailCustomization, IPropertyTypeCustomization
"EditorSubsystem", // UEditorSubsystem
"Blutility", // UEditorUtilityWidget, UAssetActionUtility
"ToolMenus", // UToolMenus
"AssetTools", // FAssetTypeActions_Base
"MyGame"
});Module skeleton — every registration in must be mirrored in :
StartupModuleShutdownModulecpp
IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)
void FMyGameEditorModule::StartupModule()
{
FPropertyEditorModule& PropMod =
FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropMod.RegisterCustomClassLayout(
UMyDataAsset::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(
&FMyDataAssetCustomization::MakeInstance));
PropMod.NotifyCustomizationModuleChanged();
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(
this, &FMyGameEditorModule::RegisterMenus));
}
void FMyGameEditorModule::ShutdownModule()
{
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor")
.UnregisterCustomClassLayout(UMyDataAsset::StaticClass()->GetFName());
}
UToolMenus::UnRegisterStartupCallback(this);
if (UToolMenus* TM = UToolMenus::TryGet()) { TM->UnregisterOwner(this); }
}Full boilerplate with asset type actions, editor modes, and factory:references/editor-module-setup.md
所有用于扩展编辑器的代码必须存放在类型的模块中。切勿在Runtime模块中直接引入 / 头文件,除非使用保护符。
"Type": "Editor"UnrealEdPropertyEditor#if WITH_EDITORjson
{ "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" }csharp
// MyGameEditor.Build.cs — 关键依赖项
PrivateDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "UnrealEd",
"Slate", "SlateCore", "EditorStyle",
"PropertyEditor", // IDetailCustomization, IPropertyTypeCustomization
"EditorSubsystem", // UEditorSubsystem
"Blutility", // UEditorUtilityWidget, UAssetActionUtility
"ToolMenus", // UToolMenus
"AssetTools", // FAssetTypeActions_Base
"MyGame"
});模块框架 — 中的每一项注册操作都必须在中对应取消注册:
StartupModuleShutdownModulecpp
IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)
void FMyGameEditorModule::StartupModule()
{
FPropertyEditorModule& PropMod =
FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropMod.RegisterCustomClassLayout(
UMyDataAsset::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(
&FMyDataAssetCustomization::MakeInstance));
PropMod.NotifyCustomizationModuleChanged();
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(
this, &FMyGameEditorModule::RegisterMenus));
}
void FMyGameEditorModule::ShutdownModule()
{
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor")
.UnregisterCustomClassLayout(UMyDataAsset::StaticClass()->GetFName());
}
UToolMenus::UnRegisterStartupCallback(this);
if (UToolMenus* TM = UToolMenus::TryGet()) { TM->UnregisterOwner(this); }
}包含资源类型操作、编辑器模式和工厂的完整模板:references/editor-module-setup.md
Editor Utility Widgets
Editor Utility Widgets
UEditorUtilityWidgetEditorUtilityWidget.hUUserWidgetcpp
#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 BatchRenameSelectedAssets(const FString& Prefix);
};
// .cpp
void UMyEditorUtilityWidget::BatchRenameSelectedAssets(const FString& Prefix)
{
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
if (Asset)
{
const FString Src = Asset->GetOutermost()->GetName(); // e.g. /Game/Folder/OldName
UEditorAssetLibrary::RenameAsset(Src, FPaths::GetPath(Src) / Prefix + TEXT("_") + Asset->GetName());
}
}
}Key functions (from ):
UEditorUtilityLibraryEditorUtilityLibrary.h| Function | Purpose |
|---|---|
| Currently selected Content Browser assets |
| Filter selection by class |
| Delete asset; |
| Selected level actors |
| Sync content browser view |
Content browser filters: Use with for programmatic asset queries by class, path, or tags.
FARFilterIAssetRegistry::GetAssetsOpen a widget programmatically:
cpp
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
->SpawnAndRegisterTab(LoadObject<UEditorUtilityWidgetBlueprint>(
nullptr, TEXT("/Game/EditorWidgets/BP_MyTool")));UEditorUtilityWidgetEditorUtilityWidget.hUUserWidgetcpp
#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 BatchRenameSelectedAssets(const FString& Prefix);
};
// .cpp
void UMyEditorUtilityWidget::BatchRenameSelectedAssets(const FString& Prefix)
{
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
if (Asset)
{
const FString Src = Asset->GetOutermost()->GetName(); // e.g. /Game/Folder/OldName
UEditorAssetLibrary::RenameAsset(Src, FPaths::GetPath(Src) / Prefix + TEXT("_") + Asset->GetName());
}
}
}UEditorUtilityLibraryEditorUtilityLibrary.h| 函数 | 用途 |
|---|---|
| 获取内容浏览器中当前选中的资源 |
| 按类筛选选中的资源 |
| 删除资源;使用 |
| 获取关卡中选中的Actor |
| 同步内容浏览器视图 |
内容浏览器筛选:使用结合,可通过类、路径或标签以编程方式查询资源。
FARFilterIAssetRegistry::GetAssets以编程方式打开Widget:
cpp
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
->SpawnAndRegisterTab(LoadObject<UEditorUtilityWidgetBlueprint>(
nullptr, TEXT("/Game/EditorWidgets/BP_MyTool")));Blutility: Scripted Actions
Blutility: 脚本化操作
UAssetActionUtility — Asset Right-Click Actions
UAssetActionUtility — 资源右键操作
Any on a subclass appears in the Content Browser context menu. Set in Class Defaults to filter by asset type.
UFUNCTION(CallInEditor)UAssetActionUtilitySupportedClassescpp
#pragma once
#include "AssetActionUtility.h" // Engine/Source/Editor/Blutility/Classes/AssetActionUtility.h
#include "MyAssetActionUtility.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyAssetActionUtility : public UAssetActionUtility
{
GENERATED_BODY()
public:
UFUNCTION(CallInEditor, Category = "My Tools")
void SetTextureCompressionToUI()
{
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
if (UTexture2D* Tex = Cast<UTexture2D>(Asset))
{
Tex->CompressionSettings = TC_EditorIcon;
Tex->MarkPackageDirty();
Tex->PostEditChange();
}
}
}
};UAssetActionUtilityUFUNCTION(CallInEditor)SupportedClassescpp
#pragma once
#include "AssetActionUtility.h" // Engine/Source/Editor/Blutility/Classes/AssetActionUtility.h
#include "MyAssetActionUtility.generated.h"
UCLASS()
class MYGAMEEDITOR_API UMyAssetActionUtility : public UAssetActionUtility
{
GENERATED_BODY()
public:
UFUNCTION(CallInEditor, Category = "My Tools")
void SetTextureCompressionToUI()
{
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
{
if (UTexture2D* Tex = Cast<UTexture2D>(Asset))
{
Tex->CompressionSettings = TC_EditorIcon;
Tex->MarkPackageDirty();
Tex->PostEditChange();
}
}
}
};UActorActionUtility — Actor Right-Click Actions
UActorActionUtility — Actor右键操作
Same pattern for level actors. Both and inherit from , the base class for all Blutility actions.
UAssetActionUtilityUActorActionUtilityUEditorUtilityObject关卡Actor的操作模式与上述一致。和均继承自,这是所有Blutility操作的基类。
UAssetActionUtilityUActorActionUtilityUEditorUtilityObjectDetail Customizations
详情定制
Class Customization — IDetailCustomization
类定制 — IDetailCustomization
cpp
class FMyDataAssetCustomization : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
// Override the TSharedPtr overload too — store WeakBuilder for async ForceRefreshDetails
virtual void CustomizeDetails(const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder) override;
private:
TWeakPtr<IDetailLayoutBuilder> WeakBuilder;
};
// In CustomizeDetails: EditCategory, AddProperty, AddCustomRow, HideCategory
// Register: PropMod.RegisterCustomClassLayout(UMyClass::StaticClass()->GetFName(), ...MakeInstance)
// Unregister in ShutdownModule: PropMod.UnregisterCustomClassLayout(...)cpp
class FMyDataAssetCustomization : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
// 同时重写TSharedPtr重载版本 — 存储WeakBuilder用于异步ForceRefreshDetails
virtual void CustomizeDetails(const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder) override;
private:
TWeakPtr<IDetailLayoutBuilder> WeakBuilder;
};
// 在CustomizeDetails中:EditCategory、AddProperty、AddCustomRow、HideCategory
// 注册:PropMod.RegisterCustomClassLayout(UMyClass::StaticClass()->GetFName(), ...MakeInstance)
// 在ShutdownModule中取消注册:PropMod.UnregisterCustomClassLayout(...)Struct Customization — IPropertyTypeCustomization
结构体定制 — IPropertyTypeCustomization
cpp
class FMyStructCustomization : public IPropertyTypeCustomization
{
public:
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> Handle,
FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& Utils) override;
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> Handle,
IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& Utils) override;
};
// Register: PropMod.RegisterCustomPropertyTypeLayout(FMyStruct::StaticStruct()->GetFName(), ...MakeInstance)Full implementations, Slate row patterns, IPropertyHandle read/write, NameContent/ValueContent:references/detail-customization-patterns.md
cpp
class FMyStructCustomization : public IPropertyTypeCustomization
{
public:
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> Handle,
FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& Utils) override;
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> Handle,
IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& Utils) override;
};
// 注册:PropMod.RegisterCustomPropertyTypeLayout(FMyStruct::StaticStruct()->GetFName(), ...MakeInstance)完整实现、Slate行模式、IPropertyHandle读写、NameContent/ValueContent:references/detail-customization-patterns.md
Custom Editor Modes
自定义编辑器模式
FEdModeEdMode.hcpp
// MyEditorMode.h
class FMyEditorMode : public FEdMode
{
public:
static const FEditorModeID EM_MyMode;
FMyEditorMode();
virtual void Enter() override;
virtual void Exit() override;
virtual bool HandleClick(FEditorViewportClient*, HHitProxy*,
const FViewportClick&) override;
virtual bool MouseMove(FEditorViewportClient* ViewportClient,
FViewport* Viewport, int32 X, int32 Y) override;
// Use View->DeprojectFVector2D (FSceneView) for world-space ray from pixel coords
virtual void Render(const FSceneView*, FViewport*,
FPrimitiveDrawInterface*) override;
virtual bool UsesToolkits() const override { return true; }
};
// MyEditorMode.cpp
const FEditorModeID FMyEditorMode::EM_MyMode = TEXT("EM_MyEditorMode");
FMyEditorMode::FMyEditorMode()
{
Info = FEditorModeInfo(EM_MyMode,
FText::FromString("My Editor Mode"), FSlateIcon(), /*bVisible=*/true);
}
void FMyEditorMode::Enter()
{
FEdMode::Enter();
if (!Toolkit.IsValid())
{
Toolkit = MakeShareable(new FMyEditorModeToolkit);
Toolkit->Init(Owner->GetToolkitHost());
}
}
void FMyEditorMode::Exit()
{
if (Toolkit.IsValid())
FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
FEdMode::Exit();
}
void FMyEditorMode::Render(const FSceneView* View, FViewport* Viewport,
FPrimitiveDrawInterface* PDI)
{
FEdMode::Render(View, Viewport, PDI);
DrawWireSphere(PDI, FVector::ZeroVector, FLinearColor::Green, 50.f, 16, SDPG_World);
}Register/unregister in module lifecycle:
cpp
// StartupModule
FEditorModeRegistry::Get().RegisterMode<FMyEditorMode>(
FMyEditorMode::EM_MyMode, FText::FromString("My Mode"), FSlateIcon(), true);
// ShutdownModule
FEditorModeRegistry::Get().UnregisterMode(FMyEditorMode::EM_MyMode);FEdModeEdMode.hcpp
// MyEditorMode.h
class FMyEditorMode : public FEdMode
{
public:
static const FEditorModeID EM_MyMode;
FMyEditorMode();
virtual void Enter() override;
virtual void Exit() override;
virtual bool HandleClick(FEditorViewportClient*, HHitProxy*,
const FViewportClick&) override;
virtual bool MouseMove(FEditorViewportClient* ViewportClient,
FViewport* Viewport, int32 X, int32 Y) override;
// 使用View->DeprojectFVector2D (FSceneView)将像素坐标转换为世界空间射线
virtual void Render(const FSceneView*, FViewport*,
FPrimitiveDrawInterface*) override;
virtual bool UsesToolkits() const override { return true; }
};
// MyEditorMode.cpp
const FEditorModeID FMyEditorMode::EM_MyMode = TEXT("EM_MyEditorMode");
FMyEditorMode::FMyEditorMode()
{
Info = FEditorModeInfo(EM_MyMode,
FText::FromString("My Editor Mode"), FSlateIcon(), /*bVisible=*/true);
}
void FMyEditorMode::Enter()
{
FEdMode::Enter();
if (!Toolkit.IsValid())
{
Toolkit = MakeShareable(new FMyEditorModeToolkit);
Toolkit->Init(Owner->GetToolkitHost());
}
}
void FMyEditorMode::Exit()
{
if (Toolkit.IsValid())
FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
FEdMode::Exit();
}
void FMyEditorMode::Render(const FSceneView* View, FViewport* Viewport,
FPrimitiveDrawInterface* PDI)
{
FEdMode::Render(View, Viewport, PDI);
DrawWireSphere(PDI, FVector::ZeroVector, FLinearColor::Green, 50.f, 16, SDPG_World);
}在模块生命周期中注册/取消注册:
cpp
// StartupModule
FEditorModeRegistry::Get().RegisterMode<FMyEditorMode>(
FMyEditorMode::EM_MyMode, FText::FromString("My Mode"), FSlateIcon(), true);
// ShutdownModule
FEditorModeRegistry::Get().UnregisterMode(FMyEditorMode::EM_MyMode);Asset Type Actions
资源类型操作
FAssetTypeActions_BaseAssetTypeActions_Base.hcpp
class FMyAssetTypeActions : public FAssetTypeActions_Base
{
public:
virtual FText GetName() const override { return FText::FromString("My Asset"); }
virtual FColor GetTypeColor() const override { return FColor(200, 100, 50); }
virtual UClass* GetSupportedClass() const override { return UMyAsset::StaticClass(); }
virtual uint32 GetCategories() override { return EAssetTypeCategories::Misc; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects,
TSharedPtr<IToolkitHost> EditWithinLevelEditor) override
{
for (UObject* Obj : InObjects)
if (UMyAsset* Asset = Cast<UMyAsset>(Obj))
MakeShareable(new FMyAssetEditor)->InitMyEditor(
EToolkitMode::Standalone, EditWithinLevelEditor, Asset);
}
};Register in , keep reference, unregister in .
StartupModuleShutdownModuleFAssetTypeActions_BaseAssetTypeActions_Base.hcpp
class FMyAssetTypeActions : public FAssetTypeActions_Base
{
public:
virtual FText GetName() const override { return FText::FromString("My Asset"); }
virtual FColor GetTypeColor() const override { return FColor(200, 100, 50); }
virtual UClass* GetSupportedClass() const override { return UMyAsset::StaticClass(); }
virtual uint32 GetCategories() override { return EAssetTypeCategories::Misc; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects,
TSharedPtr<IToolkitHost> EditWithinLevelEditor) override
{
for (UObject* Obj : InObjects)
if (UMyAsset* Asset = Cast<UMyAsset>(Obj))
MakeShareable(new FMyAssetEditor)->InitMyEditor(
EToolkitMode::Standalone, EditWithinLevelEditor, Asset);
}
};在中注册,保留引用,在中取消注册。
StartupModuleShutdownModuleFAssetEditorToolkit — Tab-Based Asset Editor Window
FAssetEditorToolkit — 基于标签页的资源编辑器窗口
cpp
class FMyAssetEditor : public FAssetEditorToolkit
{
public:
void InitMyEditor(EToolkitMode::Type Mode, TSharedPtr<IToolkitHost> Host, UMyAsset* Asset);
virtual FName GetToolkitFName() const override { return "MyAssetEditor"; }
virtual FText GetBaseToolkitName() const override { return INVTEXT("My Asset Editor"); }
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
};
// Call InitAssetEditor() inside InitMyEditor to set up the tab layout via FTabManager::NewLayoutQuery open editors programmatically via :
IAssetEditorInstancecpp
IAssetEditorInstance* Editor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/false);
if (Editor) Editor->FocusWindow(MyAsset); // CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed) — no-arg form deprecated 5.3cpp
class FMyAssetEditor : public FAssetEditorToolkit
{
public:
void InitMyEditor(EToolkitMode::Type Mode, TSharedPtr<IToolkitHost> Host, UMyAsset* Asset);
virtual FName GetToolkitFName() const override { return "MyAssetEditor"; }
virtual FText GetBaseToolkitName() const override { return INVTEXT("My Asset Editor"); }
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
};
// 在InitMyEditor中调用InitAssetEditor(),通过FTabManager::NewLayout设置标签页布局通过以编程方式查询已打开的编辑器:
IAssetEditorInstancecpp
IAssetEditorInstance* Editor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/false);
if (Editor) Editor->FocusWindow(MyAsset); // CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed) — 无参形式在5.3版本已废弃Asset Factories
资源工厂
UFactorycpp
UCLASS()
class UMyDataFactory : public UFactory
{
GENERATED_BODY()
public:
UMyDataFactory()
{
SupportedClass = UMyDataAsset::StaticClass();
bCreateNew = true; // appears in "Add" menu
bEditAfterNew = true; // opens editor after creation
}
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent,
FName InName, EObjectFlags Flags, UObject* Context,
FFeedbackContext* Warn) override
{
return NewObject<UMyDataAsset>(InParent, InClass, InName, Flags);
}
};UFactorycpp
UCLASS()
class UMyDataFactory : public UFactory
{
GENERATED_BODY()
public:
UMyDataFactory()
{
SupportedClass = UMyDataAsset::StaticClass();
bCreateNew = true; // 显示在“添加”菜单中
bEditAfterNew = true; // 创建后打开编辑器
}
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent,
FName InName, EObjectFlags Flags, UObject* Context,
FFeedbackContext* Warn) override
{
return NewObject<UMyDataAsset>(InParent, InClass, InName, Flags);
}
};Custom Thumbnails
自定义缩略图
cpp
UCLASS()
class UMyThumbnailRenderer : public UThumbnailRenderer
{
GENERATED_BODY()
virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height,
FRenderTarget* Target, FCanvas* Canvas, bool bAdditionalViewFamily) override;
};
// Register in module startup:
UThumbnailManager::Get().RegisterCustomRenderer(UMyDataAsset::StaticClass(),
UMyThumbnailRenderer::StaticClass());cpp
UCLASS()
class UMyThumbnailRenderer : public UThumbnailRenderer
{
GENERATED_BODY()
virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height,
FRenderTarget* Target, FCanvas* Canvas, bool bAdditionalViewFamily) override;
};
// 在模块启动时注册:
UThumbnailManager::Get().RegisterCustomRenderer(UMyDataAsset::StaticClass(),
UMyThumbnailRenderer::StaticClass());Editor Subsystems
编辑器子系统
UEditorSubsystemEditorSubsystem.hGEditor->GetEditorSubsystem<T>()cpp
UCLASS()
class MYGAMEEDITOR_API UMyEditorSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override
{
Super::Initialize(Collection);
FEditorDelegates::PostUndoRedo.AddUObject(this, &UMyEditorSubsystem::OnPostUndoRedo);
}
virtual void Deinitialize() override
{
FEditorDelegates::PostUndoRedo.RemoveAll(this);
Super::Deinitialize();
}
};Built-in subsystems:
| Subsystem | Purpose |
|---|---|
| Select, spawn, delete level actors |
| Load, save, duplicate assets |
| Open, save, manage levels |
| Spawn editor utility widget tabs |
UEditorSubsystemEditorSubsystem.hGEditor->GetEditorSubsystem<T>()cpp
UCLASS()
class MYGAMEEDITOR_API UMyEditorSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override
{
Super::Initialize(Collection);
FEditorDelegates::PostUndoRedo.AddUObject(this, &UMyEditorSubsystem::OnPostUndoRedo);
}
virtual void Deinitialize() override
{
FEditorDelegates::PostUndoRedo.RemoveAll(this);
Super::Deinitialize();
}
};内置子系统:
| 子系统 | 用途 |
|---|---|
| 选择、生成、删除关卡Actor |
| 加载、保存、复制资源 |
| 打开、保存、管理关卡 |
| 生成编辑器实用Widget标签页 |
Menu and Toolbar Extensions (UToolMenus)
菜单与工具栏扩展(UToolMenus)
UToolMenus (UE5+) replaces . Register inside the startup callback so Slate is ready:
FExtendercpp
void FMyGameEditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
// Main menu
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
Menu->FindOrAddSection("MyGame").AddMenuEntry("OpenMyPanel",
FText::FromString("My Panel"), FText::GetEmpty(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings"),
FUIAction(FExecuteAction::CreateLambda([]() {
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
->SpawnAndRegisterTab(/* WidgetBP */nullptr);
})));
// Toolbar button
UToolMenu* TB = UToolMenus::Get()->ExtendMenu(
"LevelEditor.LevelEditorToolBar.PlayToolBar");
TB->FindOrAddSection("MyTools").AddEntry(
FToolMenuEntry::InitToolBarButton("MyBtn",
FUIAction(FExecuteAction::CreateLambda([]() {})),
FText::FromString("My Tool"), FText::GetEmpty(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Toolbar.Settings")));
}Always unregister: + .
UToolMenus::UnRegisterStartupCallback(this)TryGet()->UnregisterOwner(this)UToolMenus(UE5+)替代了。需在启动回调中注册,确保Slate已准备就绪:
FExtendercpp
void FMyGameEditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
// 主菜单
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
Menu->FindOrAddSection("MyGame").AddMenuEntry("OpenMyPanel",
FText::FromString("My Panel"), FText::GetEmpty(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings"),
FUIAction(FExecuteAction::CreateLambda([]() {
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
->SpawnAndRegisterTab(/* WidgetBP */nullptr);
})));
// 工具栏按钮
UToolMenu* TB = UToolMenus::Get()->ExtendMenu(
"LevelEditor.LevelEditorToolBar.PlayToolBar");
TB->FindOrAddSection("MyTools").AddEntry(
FToolMenuEntry::InitToolBarButton("MyBtn",
FUIAction(FExecuteAction::CreateLambda([]() {})),
FText::FromString("My Tool"), FText::GetEmpty(),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Toolbar.Settings")));
}务必取消注册: + 。
UToolMenus::UnRegisterStartupCallback(this)TryGet()->UnregisterOwner(this)Command Registration — TCommands Pattern
命令注册 — TCommands模式
cpp
class FMyCommands : public TCommands<FMyCommands>
{
public:
FMyCommands() : TCommands("MyEditor", INVTEXT("My Editor"), NAME_None, FAppStyle::GetAppStyleSetName()) {}
virtual void RegisterCommands() override;
TSharedPtr<FUICommandInfo> OpenPanel;
};
void FMyCommands::RegisterCommands()
{
UI_COMMAND(OpenPanel, "My Panel", "Opens the panel", EUserInterfaceActionType::Button, FInputChord());
}
// In StartupModule: FMyCommands::Register(); bind via CommandList->MapAction(...)
// In ShutdownModule: FMyCommands::Unregister();cpp
class FMyCommands : public TCommands<FMyCommands>
{
public:
FMyCommands() : TCommands("MyEditor", INVTEXT("My Editor"), NAME_None, FAppStyle::GetAppStyleSetName()) {}
virtual void RegisterCommands() override;
TSharedPtr<FUICommandInfo> OpenPanel;
};
void FMyCommands::RegisterCommands()
{
UI_COMMAND(OpenPanel, "My Panel", "Opens the panel", EUserInterfaceActionType::Button, FInputChord());
}
// 在StartupModule中:FMyCommands::Register(); 通过CommandList->MapAction(...)绑定
// 在ShutdownModule中:FMyCommands::Unregister();Data Validation
数据验证
cpp
// UEditorValidatorBase — auto-discovered by Editor > Tools > Validate Assets
UCLASS()
class UMyValidator : public UEditorValidatorBase
{
GENERATED_BODY()
virtual bool CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const override
{ return InObject && InObject->IsA<UMyDataAsset>(); }
virtual EDataValidationResult ValidateLoadedAsset_Implementation(
const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context) override;
};
// Also runnable via commandlet: UnrealEditor-Cmd -run=DataValidationcpp
// UEditorValidatorBase — 编辑器会自动发现(路径:编辑器 > 工具 > 验证资源)
UCLASS()
class UMyValidator : public UEditorValidatorBase
{
GENERATED_BODY()
virtual bool CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const override
{ return InObject && InObject->IsA<UMyDataAsset>(); }
virtual EDataValidationResult ValidateLoadedAsset_Implementation(
const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context) override;
};
// 也可通过命令行工具运行:UnrealEditor-Cmd -run=DataValidationCommon Mistakes
常见错误
| Mistake | Fix |
|---|---|
| Editor headers in Runtime modules | Move to Editor module; use |
No | Always pair register/unregister; crashes Live Coding reload |
| Raw pointer capture in Slate lambdas | Capture as |
| Use |
| Editor-only; will not exist in packaged builds |
| Use only when layout structure changes; use handles/attributes for values |
| Crashes on plugin reload |
| 错误 | 修复方案 |
|---|---|
| Runtime模块中引入编辑器头文件 | 将代码移至Editor模块;如需在Runtime侧添加编辑器钩子,使用 |
| 注册与取消注册必须成对出现;否则会导致实时编码重载崩溃 |
| Slate Lambda中捕获裸指针 | 使用 |
编辑器扩展模块设置 | 对于注册菜单或定制内容的模块,使用 |
Runtime模块中引用 | 该类仅适用于编辑器;打包构建中不存在 |
每次值变更都调用 | 仅在布局结构变更时使用;对于值变更,使用句柄/属性 |
调用 | 插件重载时会崩溃 |
Related Skills
相关技能
- ue-ui-umg-slate — Slate widget fundamentals (SNew, TAttribute, FReply, SBox, SHorizontalBox)
- ue-module-build-system — Editor module , LoadingPhase,
.Build.csguardsWITH_EDITOR - ue-data-assets-tables — Custom UDataAsset types that need asset editors and type actions
- ue-cpp-foundations — UPROPERTY, UFUNCTION, UObject reflection system
- ue-ui-umg-slate — Slate Widget基础(SNew、TAttribute、FReply、SBox、SHorizontalBox)
- ue-module-build-system — 编辑器模块、LoadingPhase、
.Build.cs保护符WITH_EDITOR - ue-data-assets-tables — 需要资源编辑器和类型操作的自定义UDataAsset类型
- ue-cpp-foundations — UPROPERTY、UFUNCTION、UObject反射系统