在上一篇我们简单介绍了UE4下Plugin的扩展步骤, 这次我们再研究下UE4中实现Custom Editor窗口的过程。

UE4中我们最常用的编辑窗口就是默认的Level Editor 的窗口, 它提供我们对场景内任意物件进行编辑预览的功能。UE中默认的例如:动作、粒子、角色等,都有一个独立的Editor预览窗口。如果我们想对自定义的Asset文件做一些操作和显示上的自定义,那么我们就需要实现Custom Editor。

一, 常规Editor窗口的组成

一个完整的Editor窗口的组成:

1, 顶部菜单栏 : 快捷菜单的扩展;
2, 工具栏 : 工具栏按钮的自定义;
3, 细节面板 : 显示细节参数;
4, 视图窗口 : 预览对象的窗口, 所见即所得;

二, 窗口的布局(简单介绍Slate)

UE4的Editor下使用Slate来对窗口个的多个子窗口进行布局, 其中SCompoundWidget是窗口组件的主要基类。看下如下代码, 简单熟悉下Slate的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TabLayout = FTabManager::NewLayout("Ability_AbilityTimelineEditMode_Layout_v1")
->AddArea(
FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical) //垂直布局
->Split
(
//Toolbar
FTabManager::NewStack()
->SetSizeCoefficient(0.1f) //尺寸
->SetHideTabWell(true) //是否隐藏标签页
->AddTab(AceEditor->GetToolbarTabId(), ETabState::OpenedTab)
)
->Split
(
//此处是个视图窗口
FTabManager::NewSplitter() //新的窗口分隔
->SetOrientation(Orient_Horizontal) //水平布局
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->SetHideTabWell(false)
->AddTab(FAceEditorViewportSummoner::ID, ETabState::OpenedTab)
)
->Split
(
//此处是属性详细页
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->SetHideTabWell(false)
->AddTab(FAcePropertiesSummoner::ID, ETabState::OpenedTab)
)
)
);

三,构建窗口的过程

在上一篇中, 我们简单介绍了如果自定义Asset并实现菜单中右键创建的过程。其中在FAssetTypeActions_Blueprint的OpenAssetEditor函数中, 我们将之前在UFactory创建的Asset对象, 传入新创建的Editor的初始化函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;

for (UObject* Object : InObjects)
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(Object))
{
bool isFirstCreate = Blueprint->bIsNewlyCreated;
TSharedRef<FPathFollowerEditor> Editor(new FPathFollowerEditor());
TArray<UBlueprint*> Blueprints;
Blueprints.Add(Blueprint);

//这一步很重要
Editor->InitEditor(Mode, EditWithinLevelEditor, Blueprints, ShouldUseDataOnlyEditor(Blueprint));
}
}
}

在来到我们自定义的FBlueprintEditor中, 看下InitEditor的函数实现:

1
2
3
4
5
6
7
8
9
void InitEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, const TArray<UBlueprint*>& InBlueprints, bool ShouldUseDataOnlyEditor)
{
TArray<UObject*> ObjectsBeingEditted;
for (UBlueprint* BP : InBlueprints)
ObjectsBeingEditted.Add(BP);

//调用默认的蓝图编辑窗口, 如果我们需要自定义就修改这里.
InitBlueprintEditor(Mode, IniInitEditortToolkitHost, InBlueprints, false);
}

InitBlueprintEditor是UE4提供的默认的蓝图类编辑接口, 它会根据我们在工厂类中对我们自定义的蓝图Asset文件的类型来调用对应的蓝图编辑器, 例如是Actor, Animation或者Particle。但是如果我们想实现自定义的蓝图类型, 并且自定义对应的操作,那我们需要在InitEditor函数中进行扩展。

在自定义的InitBlueprintEditor中, 我们通过自定义实现FApplicationMode的方式, 来切换当前运行的模式, 进入如上图所示开始初始化的整个流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void InitBlueprintEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, const TArray<UBlueprint*>& InAbilityBlueprints, bool ShouldUseDataOnlyEditor)
{
TSharedPtr<FAceEditor> Editor(SharedThis(this));

if (!Toolbar.IsValid())
Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this)));

TArray<UObject*> ObjectsBeingEditted;
for(UBlueprint* BP : InAbilityBlueprints)
ObjectsBeingEditted.Add(BP);

//Create mode.
TSharedRef<FApplicationMode> ViewMode = MakeShareable(new FAceEditorViewMode(Editor));
AddApplicationMode(ViewMode->GetModeName(), ViewMode);

const TSharedRef<FTabManager::FLayout> DummyLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea());
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;

FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, "AceEditorApp", DummyLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsBeingEditted);

SetCurrentMode(ViewMode->GetModeName());
}

简单介绍下这几个类的作用及他们的关系:

  • SCompoundWidget: 大部分窗口部件的基类;
  • FWorkflowTabFactory: FTab标签页自定义工厂;
  • FPreviewScene: 封装用于预览或缩略图呈现的场景, 个人理解是主要负责预览场景的渲染;
  • FEditorViewportClient: 视图窗口, 对摄像机移动,渲染调试, 鼠标点击等一些操作的高级封装;
  • SSingleObjectDetailsPanel: 细节面板的基类, 对一些属性进行可视化编辑

四,延伸扩展

默认的Custom Editor中的初始化的对象, 只支持点击不支持鼠标移动等操作。如果需要类似Level Editor中的鼠标拖动等操作, 需要对这块的部分接口做扩展。还有一点需要注意的在Editor的下屏幕空间的拾取,决定了窗口里的一些鼠标点击等事件,所以所有需要参与点击类的Component, 必须有AActor作为对应的Owner,否则没法参与屏幕拾取~

在UE中, 需要渲染的可点击对象都会生成一个HitProxy数据,每一个HitProxy包含一个唯一的INT32的唯一标识码, HitProxyId。在渲染时, UE4会将HitProxyId转换成颜色, 写入一块宽高和屏幕瞪大的数组中, 这就是HitProxyMap。当每次鼠标移动的时候,根据对应屏幕的xy坐标索引得到对应的HitProxyId, 再根据这个Id得到对应的HitProxy数据。