ue-editor-tools

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE Editor Tools

UE Editor工具

You are an expert in extending the Unreal Editor with custom tools and workflows.
你是一位精通使用自定义工具与工作流扩展Unreal Editor的专家。

Context

上下文

Read
.agents/ue-project-context.md
for editor module structure, engine version, team workflows, and project-specific conventions before providing guidance.
在提供指导前,请阅读
.agents/ue-project-context.md
以了解编辑器模块结构、引擎版本、团队工作流以及项目特定规范。

Before 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
"Type": "Editor"
. Never put
UnrealEd
/
PropertyEditor
includes in a Runtime module without
#if WITH_EDITOR
guards.
json
{ "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
StartupModule
must be mirrored in
ShutdownModule
:
cpp
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

所有用于扩展编辑器的代码必须存放在
"Type": "Editor"
类型的模块中。切勿在Runtime模块中直接引入
UnrealEd
/
PropertyEditor
头文件,除非使用
#if WITH_EDITOR
保护符。
json
{ "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"
});
模块框架 —
StartupModule
中的每一项注册操作都必须在
ShutdownModule
中对应取消注册:
cpp
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

UEditorUtilityWidget
(from
EditorUtilityWidget.h
) extends
UUserWidget
for editor-only UMG panels. Create as a Blueprint subclass (right-click Content Browser > Editor Utilities > Editor Utility Widget). Run: right-click the widget asset > Run Editor Utility Widget. Or subclass in C++:
cpp
#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
UEditorUtilityLibrary
functions (from
EditorUtilityLibrary.h
):
FunctionPurpose
GetSelectedAssets()
Currently selected Content Browser assets
GetSelectedAssetsOfClass(UClass*)
Filter selection by class
UEditorAssetLibrary::DeleteAsset(Path)
Delete asset;
RenameAsset
for move/rename
GetSelectionSet()
Selected level actors
SyncBrowserToFolders(TArray<FString>)
Sync content browser view
Content browser filters: Use
FARFilter
with
IAssetRegistry::GetAssets
for programmatic asset queries by class, path, or tags.
Open a widget programmatically:
cpp
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
    ->SpawnAndRegisterTab(LoadObject<UEditorUtilityWidgetBlueprint>(
        nullptr, TEXT("/Game/EditorWidgets/BP_MyTool")));

UEditorUtilityWidget
(来自
EditorUtilityWidget.h
)继承自
UUserWidget
,用于编辑器专属的UMG面板。可创建Blueprint子类(右键点击内容浏览器 > 编辑器工具 > Editor Utility Widget)。运行方式:右键点击Widget资源 > 运行Editor Utility Widget。也可通过C++子类化:
cpp
#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());
        }
    }
}
UEditorUtilityLibrary
的关键函数(来自
EditorUtilityLibrary.h
):
函数用途
GetSelectedAssets()
获取内容浏览器中当前选中的资源
GetSelectedAssetsOfClass(UClass*)
按类筛选选中的资源
UEditorAssetLibrary::DeleteAsset(Path)
删除资源;使用
RenameAsset
进行移动/重命名
GetSelectionSet()
获取关卡中选中的Actor
SyncBrowserToFolders(TArray<FString>)
同步内容浏览器视图
内容浏览器筛选:使用
FARFilter
结合
IAssetRegistry::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
UFUNCTION(CallInEditor)
on a
UAssetActionUtility
subclass appears in the Content Browser context menu. Set
SupportedClasses
in Class Defaults to filter by asset type.
cpp
#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();
            }
        }
    }
};
UAssetActionUtility
子类中的任何
UFUNCTION(CallInEditor)
都会出现在内容浏览器的上下文菜单中。在类默认值中设置
SupportedClasses
可按资源类型筛选。
cpp
#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
UAssetActionUtility
and
UActorActionUtility
inherit from
UEditorUtilityObject
, the base class for all Blutility actions.

关卡Actor的操作模式与上述一致。
UAssetActionUtility
UActorActionUtility
均继承自
UEditorUtilityObject
,这是所有Blutility操作的基类。

Detail 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

自定义编辑器模式

FEdMode
(from
EdMode.h
) provides specialized viewport interaction. Register globally; only one active per viewport at a time.
cpp
// 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);

FEdMode
(来自
EdMode.h
)提供专属的视口交互功能。全局注册;每个视口同一时间只能激活一个模式。
cpp
// 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_Base
(from
AssetTypeActions_Base.h
) controls Content Browser appearance and context menu for custom asset types.
cpp
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
StartupModule
, keep reference, unregister in
ShutdownModule
.
FAssetTypeActions_Base
(来自
AssetTypeActions_Base.h
)控制自定义资源类型在内容浏览器中的显示和上下文菜单。
cpp
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);
    }
};
StartupModule
中注册,保留引用,在
ShutdownModule
中取消注册。

FAssetEditorToolkit — 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::NewLayout
Query open editors programmatically via
IAssetEditorInstance
:
cpp
IAssetEditorInstance* Editor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
    ->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/false);
if (Editor) Editor->FocusWindow(MyAsset);  // CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed) — no-arg form deprecated 5.3
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;
};
// 在InitMyEditor中调用InitAssetEditor(),通过FTabManager::NewLayout设置标签页布局
通过
IAssetEditorInstance
以编程方式查询已打开的编辑器:
cpp
IAssetEditorInstance* Editor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
    ->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/false);
if (Editor) Editor->FocusWindow(MyAsset);  // CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed) — 无参形式在5.3版本已废弃

Asset Factories

资源工厂

UFactory
subclasses allow the Content Browser's "Add" menu to create new custom assets:
cpp
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);
    }
};
UFactory
子类允许内容浏览器的“添加”菜单创建新的自定义资源:
cpp
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

编辑器子系统

UEditorSubsystem
(from
EditorSubsystem.h
) — editor-lifetime singletons, auto-discovered (no registration needed). Access via
GEditor->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:
SubsystemPurpose
UEditorActorSubsystem
Select, spawn, delete level actors
UEditorAssetSubsystem
Load, save, duplicate assets
ULevelEditorSubsystem
Open, save, manage levels
UEditorUtilitySubsystem
Spawn editor utility widget tabs

UEditorSubsystem
(来自
EditorSubsystem.h
)—— 编辑器生命周期内的单例对象,自动被发现(无需注册)。通过
GEditor->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();
    }
};
内置子系统:
子系统用途
UEditorActorSubsystem
选择、生成、删除关卡Actor
UEditorAssetSubsystem
加载、保存、复制资源
ULevelEditorSubsystem
打开、保存、管理关卡
UEditorUtilitySubsystem
生成编辑器实用Widget标签页

Menu and Toolbar Extensions (UToolMenus)

菜单与工具栏扩展(UToolMenus)

UToolMenus (UE5+) replaces
FExtender
. Register inside the startup callback so Slate is ready:
cpp
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+)替代了
FExtender
。需在启动回调中注册,确保Slate已准备就绪:
cpp
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=DataValidation

cpp
// 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=DataValidation

Common Mistakes

常见错误

MistakeFix
Editor headers in Runtime modulesMove to Editor module; use
#if WITH_EDITOR
for runtime-side editor hooks
No
UnregisterCustomClassLayout
in
ShutdownModule
Always pair register/unregister; crashes Live Coding reload
Raw pointer capture in Slate lambdasCapture as
TWeakPtr
; pin before use
LoadingPhase: Default
for editor extension module
Use
PostEngineInit
for modules registering menus or customizations
UEditorUtilityWidget
referenced from Runtime
Editor-only; will not exist in packaged builds
ForceRefreshDetails()
on every value change
Use only when layout structure changes; use handles/attributes for values
RegisterMode
without
UnregisterMode
Crashes on plugin reload

错误修复方案
Runtime模块中引入编辑器头文件将代码移至Editor模块;如需在Runtime侧添加编辑器钩子,使用
#if WITH_EDITOR
ShutdownModule
中未调用
UnregisterCustomClassLayout
注册与取消注册必须成对出现;否则会导致实时编码重载崩溃
Slate Lambda中捕获裸指针使用
TWeakPtr
捕获;使用前先Pin
编辑器扩展模块设置
LoadingPhase: Default
对于注册菜单或定制内容的模块,使用
PostEngineInit
Runtime模块中引用
UEditorUtilityWidget
该类仅适用于编辑器;打包构建中不存在
每次值变更都调用
ForceRefreshDetails()
仅在布局结构变更时使用;对于值变更,使用句柄/属性
调用
RegisterMode
但未调用
UnregisterMode
插件重载时会崩溃

Related Skills

相关技能

  • ue-ui-umg-slate — Slate widget fundamentals (SNew, TAttribute, FReply, SBox, SHorizontalBox)
  • ue-module-build-system — Editor module
    .Build.cs
    , LoadingPhase,
    WITH_EDITOR
    guards
  • 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 — 编辑器模块
    .Build.cs
    、LoadingPhase、
    WITH_EDITOR
    保护符
  • ue-data-assets-tables — 需要资源编辑器和类型操作的自定义UDataAsset类型
  • ue-cpp-foundations — UPROPERTY、UFUNCTION、UObject反射系统