找回密码
 立即注册
查看: 290|回复: 0

Unreal 工具开发 自定义编辑器

[复制链接]
发表于 2023-2-5 20:08 | 显示全部楼层 |阅读模式
# 前言概述:
这是一篇关于Unreal引擎 C++插件开发中关于自定义AssetEditor的开发教程,
本篇文章将会提供:
1.  一种清晰的思路去管理和开发自定义资产编辑器,方便其他开发人员使用;
2.  支持较为复杂的功能定制,使开发人员能够根据需求进行更灵活的操作;
3.  可以帮助开发人员提高对UnrealC++插件开发的技能和知识。
阅读文章可能需要的前置准备有:
了解如何在Unreal中创建自定义资产
了解Slate
了解C++基础的语法
文章最后的代码执行测试的引擎版本为:
Unreal Engine 5.0

创建与部署自定义资产编辑器类




    首先本文想先通过一张相关类的说明图片直观解释创建一个AssetEditor涉及到的类和重要的接口方法。资产编辑器开发可用的接口繁多,但是依然可以参照一个主线:编辑器主要围绕FWorkFlowCentricApplication(或FAssetEditorToolkit)类进行开发,上游需要接入FAssetTypeAction类进行入口的实现。下游则需要实现自定义的Slate类,并通过通过父类方法创建Slate控件实例并注册到编辑器布局中。
类实现首先需要通过
# 一.前置工作:
●创建了Unreal插件作为代码开发模块,支持EditorModule开发。
●通过UFactory创建了新的资产类型。

# 二. AssetEditor的创建类型
+ 引擎提供的资产编辑器类
在Unreal中定制一个自定义的编辑器界面,可以使用两个类:
FAssetEditorToolkit和它的子类FWorkflowCentricApplication,它们被用来构建工作流程和编辑器类型的应用程序。
FWorkflowCentricApplication 类除了继承了父类的接口与功能,还包含了一个用于存储多个FApplicationMode的Map图ApplicationModeList,并且能够进行Mode的切换。

+ 使用场景建议
FWorkflowCentricApplication 适用于创建游戏开发过程中用于不同阶段的工具和界面,需要支持多Mode时可以选用该模式(行为树、动画蓝图资产等都使用了这种方法),相较于AssetEditroToolkit类来说,实现并不复杂,同时支持更多的功能扩展。
FAssetEditorToolkit 适用于创建编辑器工具来编辑游戏中的资产
+ 本文的实现继承FWorkflowCentricApplication类,两种类的基础实现没有差异。

# 三. Editor类的实现
+ FCustomAssetEditor是本文案例中继承FWorkflowCentricApplication的子类,

类的基础实现中除了重载父类实现定义编辑器样式方法外,还需要实现几个关键方法:InitCustomAssetEditor方法、自定义Slate创建的方法SpawnTab_CustomWidget和RegistorTabSpawners/UnRegistorTabSpawner方法,前两个方法的命名可由开发者自行定义,RegistorTabSpawners则是重载父类方法。

## 3.1 InitCustomAssetEditor方法:
在此方法中,主要分为两个内容:
① 初始化编辑器布局,可以使用以下步骤:
1. 通过创建适当类的实例并设置其属性来定义自定义 FLayout、菜单和工具栏。
2. 自定义 Slate 窗口现在将绑定到 TabManager,并使用自定义 FLayout、菜单和工具栏显示资产编辑器。

② 调用父类方法InitAssetEditor将自定义 Slate 窗口绑定到 TabManager。

## 3.2 RegistorTabSpawners/UnRegistorTabSpawner方法:
+ 我们首先需要知道,Tab(SDockTab)是引擎定义的一个控件类,每一个Tab都是可以被关闭或打开的SWidget.Tab类型的窗口右上角包含一个关闭按钮。窗口内则可以包含自定义显示的各种Slate内容。

+ 我们打开编辑器时,就会根据Layout布局加载对应的Tab控件,并显示其中的内容,即Tab装入的子控件(Child Slate)。

+ 在此方法中,最重要的是调用TabManager对象,TabManager类主要用于管理编辑器中的布局,创建Tab对象。在类中我们需要定义一个FName类型的TabID变量,用于在该方法中指示一个Slate窗口。我们需要通过调用TabManager的方法RegisterTabSpawner绑定TabID和Slate,用于后续定义Slate控件的显示。

## 3.3 SpawnTab_CustomWidget方法
在此方法中,将会创建并返回一个SWidget实例。
而RegistorTabSpawner方法通过调用TabManager的接口,将该Slate控件实例注册,与TabID进行绑定,并能够在InitCustomAssetEditor方法中添加到布局Flayout中被显示出来。
我们不仅可以创建简单的Slate控件(如SButton、IDetailsViews),也可以创建较为复杂的窗口,如SEditorViewport(3D预览窗口)、SGraphEditor(可以连接复杂自定义的节点图表系统)等内容,并将其添加到我们的自定义编辑器中。
在本文实例中,我在该方法中创建了一个最简单的Slate类SButton。
```cpp
TSharedRef<SDockTab> FArtAssetEditor::SpawnTab_CustomTab(const FSpawnTabArgs& Args)
{
return
SNew(SDockTab)
         [
            //Add A New S Element
SNew(SButton)
            .Text(FText::FromString("Custom Tab!"))
         ];
}
```

## 3.4 类的基础实现

Editor类的实现主要包括如下几部分:
CustomAssetEditor.h

```cpp
/*CustomAssetEditor.h*/
#pragma once
//engine
#include "WorkflowOrientedApp/WorkflowCentricApplication.h"
#include "IDetailsView.h"

//project 项目文件路径和类需要自己定义实现
#include "CustomAsset.h"
#include "SCustomWidget.h"

#define LOCTEXT_NAMESPACE "CustomAssetEditor"
class FCustomAssetEditor : public FWorkflowCentricApplication
{

//Basic views information in Unreal Editor
public:

FCustomAssetEditor();
virtual ~FCustomAssetEditor();

    // FAssetEditorToolkit
FName GetToolkitFName() const override { return "toolkitFName_CustomAssetEditor"; }
FText GetBaseToolkitName() const override { return INVTEXT("BaseToolkitName_CustomAssetEditor"); }
FString GetWorldCentricTabPrefix() const override { return "Prefix_CustomAsset "; }
FLinearColor GetWorldCentricTabColorScale() const override { return {}; }
    // End of FAssetEditorToolkit*/

    //Register TabID - TabPtr
void RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager) override;
void UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager) override;

    //Init Interface
void InitCustomAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost>& InitToolkitHost, UCAsset* InCustomAsset);
    //Independence Functions

    //Custom Slate Element
static const FName CustomTabID;
TSharedRef<SDockTab> SpawnTab_CustomTab(const FSpawnTabArgs& Args);

    //Asset
public:
    TWeakObjectPtr<UCustomAsset> CustomAsset;
UCustomAsset* GetCustomAsset(){return CustomAsset.Get();}
};

#undef LOCTEXT_NAMESPACE
```
CustomAssetEditor.cpp
```cpp
/*CustomAssetEditor.cpp*/
#include "CustomAssetEditor.h"

//project 项目文件路径和类需要自己定义实现
#include "SCustomWidget.h"

#define LOCTEXT_NAMESPACE "CustomAssetEditor"
//Set Name
const FName FCustomAssetEditor::CustomTabID(TEXT("CustomAsset_Custom"));

FCustomAssetEditor::FCustomAssetEditor()
{
    CustomAsset = nullptr;
}

FCustomAssetEditor::~FCustomAssetEditor()
{
}

void FCustomAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
    //group
    WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_CustomAssetEditor", "CustomAsset Editor"));

InTabManager->RegisterTabSpawner(CustomTabID, FOnSpawnTab::CreateSP(this, &FCustomAssetEditor::SpawnTab_CustomTab))
        .SetDisplayName(LOCTEXT("Custom Tab", "CustomAsset Custom Tab"))
        .SetGroup(WorkspaceMenuCategory.ToSharedRef());
     }

void FCustomAssetEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
    //Unregister tab ptr: Details View
InTabManager->UnregisterTabSpawner(CustomTabID);
}
void FCustomAssetEditor::InitCustomAssetEditor(const EToolkitMode::Type Mode,
const TSharedPtr<IToolkitHost>& InitToolkitHost, UCustomAsset* InCustomAsset)
{
    CustomAsset = InCustomAsset;
    //layout
const TSharedRef<FTabManager::FLayout> CustomAssetEditorLayout = FTabManager::NewLayout("Layout_CustomAsset")
    ->AddArea
    (
FTabManager::NewPrimaryArea()
        ->SetOrientation(Orient_Vertical)
        ->Split
        (
FTabManager::NewStack()
            ->SetSizeCoefficient(0.1f)
            ->SetHideTabWell(true)
            ->AddTab(CustomTabID, ETabState::OpenedTab)
        )
    );
    //Init
InitAssetEditor(Mode, InitToolkitHost, FName("CustomAssetEditorApp"), CustomAssetEditorLayout, true, true, InCustomAsset);
}

TSharedRef<SDockTab> FCustomAssetEditor::SpawnTab_CustomTab(const FSpawnTabArgs& Args)
{
return
SNew(SDockTab)
         [
            //Add A New S Element
SNew(SButton)
            .Text(FText::FromString("Custom Tab!"))
         ];
}

#undef LOCTEXT_NAMESPACE

```

# 5. AssetEditor编辑器开启入口:
+ 实现Asset开启自定义编辑器,可通过继承AssetTypeAction类实现,并重载类中的OpenAssetEditor方法实现。
创建一个Asset类型后,如果没有自定义的编辑器,就会调用父类FAssetTypeAction的OpenAssetEditor方法,这个方法中创建了一个FSimpleAssetEditor类实例并调用其InitEditor方法,生成了一个带有DetailsView的简易编辑器窗口。

这里给出AssetTypeAction类的实现:

AssetTypeActions_CustomAsset.h
```cpp
#pragma once

#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"

/**
*
*/
/*AssetTypeActions class for the Motion Field so it can have a distinctive color and opens the Motion Field editor*/
class FAssetTypeActions_CustomAsset : public FAssetTypeActions_Base
{

//************* construct, override and interface
public:
FAssetTypeActions_CustomAsset();

    // IAssetTypeActions interface
    // 右键打开创建时的资产名称
virtual FText GetName() const override;
    // 资产颜色
virtual FColor GetTypeColor() const override;
    // 资产支持类
virtual UClass* GetSupportedClass() const override;
    // 右键打开创建时的资产目录
virtual uint32 GetCategories() override;
    // End of IAssetTypeActions interface

//************* end construct, override and interface

public:
    // Entry Function while open editor if Asset Has Editor
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;

};

```
AssetTypeActions_CustomAsset.cpp
```cpp
#include "CustomAssetTypeActions.h"

//project
#include "CustomEditor.h"
#include "CustomAssetEditor.h"
#include "Asset/CustomAsset.h"

FAssetTypeActions_CustomAsset::FAssetTypeActions_CustomAsset()
{
}

FText FAssetTypeActions_CustomAsset::GetName() const
{
return INVTEXT("CustomAsset");
}

FColor FAssetTypeActions_CustomAsset::GetTypeColor() const
{
return FColor::Magenta;
}

UClass* FAssetTypeActions_CustomAsset::GetSupportedClass() const
{
return UCustomAsset::StaticClass();
}

uint32 FAssetTypeActions_CustomAsset::GetCategories()
{
    //这里的Categories和注册type actions时调用的问题尚未解决
return EAssetTypeCategories::Misc;
}
// If Asset Has Editor
void FAssetTypeActions_CustomAsset::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;

for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
    {
if (UCustomAsset* CustomAsset = Cast<UCustomAsset>(*ObjIt))
        {
            TSharedRef<FCustomAssetEditor> NewCustomAssetEditor(new FCustomAssetEditor());
NewCustomAssetEditor->InitCustomAssetEditor(Mode, EditWithinLevelEditor, CustomAsset);

        }
    }
}

```
运行结果

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 06:45 , Processed in 0.089735 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表