【转载】UE4.26 几种编辑器扩展方法

2,990 阅读8分钟

原文链接:UE4.26 几种编辑器扩展方法 | Nero

正文

0. 前言

为了方便游戏开发,对编辑器扩展开发各种各样的工具是必不可少的,今天我们来看看在 UE4 中如何快速的进行编辑器扩展

1. 基于 C++ 的编辑器扩展

UE4 编辑器界面是基于 Slate 框架的,如果想要用 Slate 框架扩展编辑器,务必在你的模块构建文件 xxx.Build.cs 中添加 SlateSlateCore 两个模块

如何自定义一个菜单栏、菜单、工具栏?

首先需要知道 FExtender 类,因为它提供了三个非常有用的方法:

  • AddMenuBarExtension
  • AddMenuExtension
  • AddToolBarExtension
class FExtender
{
    public:
    // 创建一个菜单栏上的按钮
    SLATE_API TSharedRef< const FExtensionBase > AddMenuBarExtension( FName ExtensionHook, EExtensionHook::Position HookPosition, const TSharedPtr< FUICommandList >& CommandList, const FMenuBarExtensionDelegate&             MenuBarExtensionDelegate );
    // 创建一个菜单按钮
    SLATE_API TSharedRef< const FExtensionBase > AddMenuExtension( FName ExtensionHook, EExtensionHook::Position HookPosition, const TSharedPtr< FUICommandList >& CommandList, const FMenuExtensionDelegate&   MenuExtensionDelegate );
    // 创建一个工具栏按钮
    SLATE_API TSharedRef< const FExtensionBase > AddToolBarExtension( FName ExtensionHook, EExtensionHook::Position HookPosition, const TSharedPtr< FUICommandList >& CommandList, const FToolBarExtensionDelegate& ToolBarExtensionDelegate );
    ...
};

主要参数解析:

参数 ExtensionHook 是可扩展点的名字,比如 “LevelEditor”,如何快速知道这些名字?

image.png

上面那些绿色的点就是可扩展点!

参数 Hook Position 是枚举类型,具体值有:

namespace EExtensionHook
{
    enum Position
    {
        Before,
        After,
        First,
    };
}

参数 CommandList 是指令集,这里可以为 NULL ,也可以创建一个指令集类,具体如下:

  • a. 如果传入 NULL ,那么需要我们自定义 Menu 或者 ToolBar 实体,比如,添加一个菜单的代码如下:
MenuBuilder.AddMenuEntry(
    FText::FromString("TestMenu"), // 名字
    FText::FromString("This is TestMenu"), // tips
    FSlateIcon(), // 图标,这里默认是没有图标,当然可以添加图标,后面的内容再讲吧
    FUIAction(FExecuteAction::CreateRaw(this, &FTestPluginModule::MenuCallback) // 给菜单绑定一个回调函数
)
  • b. 如果想传入 CommandList,那么需要添加一个类继承于 TCommands,并在该类的 RegisterCommands 方法中用 UI_COMMAND 宏来注册自己命令
class FMyTestCommands : public TCommands<FMyTestCommands>
{
    public:
    FMyTestCommands()
    :TCommands<FMyTestCommands>(TEXT("TestPlugin"), NSLOCTEXT("Contexts", "TestPlugin", "TestPlugin Toolbar"), NAME_None, NAME_None)
    {}
    virtual void RegisterCommands() override;
    public:
    //指令 Action1、Action2
    TSharedPtr<FUICommandInfo> Action1;
    TSharedPtr<FUICommandInfo> Action2;
};
// 注册指令
void FMyTestCommands::RegisterCommands()
{
    UI_COMMAND(Action1, "Action1", "Execute Action1", EUserInterfaceActionType::Button, FInputGesture()); // FInputGesture这个是支持快捷键相关的
    UI_COMMAND(Action2, "Action2", "Execute Action2", EUserInterfaceActionType::Button, FInputGesture());
}

为指令映射一个操作

CommandList->MapAction(
    FMyTestCommands::Get().Action1,
    FExecuteAction::CreateRaw(this, &FTestPluginModule::ActionCallback1),// 将 ActionCallback1 关联到指令 Action1 上
    FCanExecuteAction()
);

接下来用指令添加一个菜单,回调函数为 Action1

MenuBuilder.AddMenuEntry(FMyTestCommands::Get().Action1);

参数 MenuBarExtensionDelegateMenuExtensionDelegateToolBarExtensionDelegate单播委托,绑定一个回调函数,分别传入参数 FMenuBarBuilderFMenuBuilderFToolBarBuilder

这三个类都是继承于 FMultiBoxBuilder

知道了 FExtender 类,还需要知道 LevelEditorModule ,用它将我们添加的菜单,菜单栏、工具栏等进行管理,因此我们需要加载这个模块

// 加载 LevelEditor 模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
 
// 将菜单扩展性添加到 LevelEditorModule
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);

上面说了一大堆,实际开发可不一定行!来个例子试一试:一个简单的多级菜单

来个完整的小白教程

  1. 创建一个 C++ 项目,名字 EditorExtendTest

  1. 修改 EditorExtendTest\Source\EditorExtendTest 文件夹下的 EditorExtendTest.Build.csEditorExtendTest.hEditorExtendTest.cpp

EditorExtendTest.Build.cs 的代码为:

using UnrealBuildTool;
public class EditorExtendTest : ModuleRules
{
    public EditorExtendTest(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
        //将Slate和SlateCore模块添加进来
        PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
    }
}

EditorExtendTest.h 的代码为:

FMenuBuilder 为例

#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FEditorExtendTestModule : public IModuleInterface
{
    public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
 
    private:
    void AddSubMenus(FMenuBuilder& MenuBuilder);
    void MenuCallback();
};

EditorExtendTest.cpp 的代码为:

FExtenderLevelEditorModuleFMenuBuilder 为例

#include "EditorExtendTest.h"
#include "Modules/ModuleManager.h"
#include "LevelEditor.h"
#include "Misc/MessageDialog.h"
#define LOCTEXT_NAMESPACE "FEditorExtendTestModule"
void FEditorExtendTestModule::StartupModule()
{
    TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
    // 用 FExtender 的 AddMenuExtension 添加一个菜单按钮
    MenuExtender->AddMenuExtension("LevelEditor", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateLambda([this](FMenuBuilder& Builder)
    {
        Builder.BeginSection("CustomMenu");
        // 创建一个 Menu1
        Builder.AddMenuEntry(
            FText::FromString("Menu1"),
            FText::FromString("This is Menu1"),
            FSlateIcon(),
            FUIAction(FExecuteAction::CreateRaw(this, &FEditorExtendTestModule::MenuCallback))
        );
        // 创建一个有子菜单的 Menu2
        Builder.AddSubMenu(
            FText::FromString("Menu2"),
            FText::FromString("This is Menu2"),
            FNewMenuDelegate::CreateRaw(this, &FEditorExtendTestModule::AddSubMenus)
        );
        Builder.EndSection();
    })
    );
    // 加载 LevelEditor 模块
    FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
    LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}
// 添加子菜单
void FEditorExtendTestModule::AddSubMenus(FMenuBuilder& Builder)
{
    Builder.AddMenuEntry(
        FText::FromString("SubMenu1"),
        FText::FromString("This is SubMenu1"),
        FSlateIcon(),
        FUIAction(FExecuteAction::CreateRaw(this, &FEditorExtendTestModule::MenuCallback))
    );
    Builder.AddMenuEntry(
        FText::FromString("SubMenu2"),
        FText::FromString("This is SubMenu2"),
        FSlateIcon(),
        FUIAction(FExecuteAction::CreateRaw(this, &FEditorExtendTestModule::MenuCallback))
    );
}
// 回调函数
void FEditorExtendTestModule::MenuCallback()
{
    FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Just a test!"));
}
 
void FEditorExtendTestModule::ShutdownModule()
{}
 
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FEditorExtendTestModule, EditorExtendTest)

全部代码如上,下面看下效果:

2. 基于蓝图的编辑器扩展

虽说蓝图由于性能等原因广受诟病,但是用作编辑器扩展,无疑是一种比较好的选择,省去了很多写代码的工作(策划也可以搞工具啦!),如果还不了解蓝图,就赶紧去学习一下

依然是开始的问题,如何自定义一个菜单栏、菜单、工具栏?

依然是一个完整的小白教程

1. 创建一个蓝图项目,名字 EditorExtendTest2

2. 使用 Editor Utility Blueprint 中的 EditorUtilityToolMenuEntry

首先需要知道 Editor Utilities,它有两个类 Editor Utility BlueprintEditor Utility Widget

操作指引:在 Content Browser 中鼠标右键,在弹出的菜单中找到 Editor Utilities

编辑器的很多功能都可以通过 Editor Utility Blueprint 来扩展,而 Editor Utility Widget 仅仅生成一个菜单按钮和一个弹出窗口(这对我们的定义工具开发很有用!)

先来看 Editor Utility Blueprint,单击 Editor Utility Blueprint 弹出如下界面,然后找到 EditorUtilityToolMenuEntry,这个东西很神奇,我们菜单、菜单栏、工具栏等都可以通过这个实现!

选择 EditorUtilityToolMenuEntry 之后会生成一个 TestToolBar.uasset 的文件,然后双击打开编辑界面

那么,接下来我们用 EditorUtilityToolMenuEntry 创建一个工具栏按钮 ToolBarButton 来详细讲解一下怎么使用(其他的东西原理一致,不一一讲解)

该编辑器右侧的 Details 面板下有个 Data 属性,对,这就是我们要修改的内容

MenuLevelEditor.LevelEditorToolBar,怎么知道要填什么名字?

操作指引:去到工程目录下的Config文件夹下(我的是 EditorExtendTest\Config ),找到 DefaultEditorPerProjectUserSettings.ini 配置文件(如果没有,就创建它),在该文件中添加如下代码

[/Script/UnrealEd.EditorExperimentalSettings]
bEnableEditToolMenusUI=True

重启工程,可以在 Window 菜单栏下面看到 Enable Menu Editing 复选框,勾上它,就会在菜单栏、工具栏、右键菜单等等上显示 Editor Menu

Section:可填可不填,主要是为按钮划分一个区域

Name:按钮名字,我填的是 TestToolBar

Label 和 Tool Tip:可填可不填,略

Icon:这个是定制图标的,如果想用编辑器系统的图标的话,可以通过一下方式得到

操作指引Window->Developer Tools->Widget Reflector

Style Set Name:样式集合名字,通常为 EditorStyle

Style Name:选一天图标,然后天上名字,我填的是 ClassThumbnail.ButtonStyleAsset

Style Style Name:有就填,没有就不填

Insert Position:按钮插入位置

Name: 填扩展点名字,我填的是 Settings,怎么知道扩展点名字?

Position:插入指定扩展点位置的前后或者里面,枚举值(FirstBeforeAfter),我选的是 After

小 tips:经验证,这里不能修改,默认值就好(原因未知),否则会出现如下提示,并且按钮注册不了!

Advanced:高级选项

Tutorial Highlight:不知道干啥用,不管它,不填即可

Entry Type:按钮类型,主要有 MenuEntryToolBarButton 等等,我选的是 ToolBarButton

User Interface Action Type:响应类型,有 ButtonToggle 等,我选的是 Button

其他的不一一详述,因为有些参数我们并不关心,最后我的设置如下:

以上操作完成了 ToolBarButton 的创建,那么如何才能在我们的工具栏中看到这个按钮?

继续上面的步骤,我们需要添加 Run 自定义事件,用它来在 UE4 编辑器启动的时候注册这个按钮

去到 TestToolBar 蓝图的编辑界面,如果你打开的是如下界面,需要点击 Open Full Blueprint Editor

在蓝图编辑界面右键添加一个名为 Run 的自定义事件,按住鼠标拖动 Run 的执行引脚放开后找到 ToolMenus 下面的 Get 方法,然后拖动 GetReturnValue 执行引脚,找到 AddMenuEntryObject 方法

在蓝图编辑器空白处右键,创建一个 self ,并连接到 AddMenuEntryObjectMenuEntryObject 位置

保存,编译

接下来,就要在配置文件中添加启动项了,详细内容可参考官方文档点这里

操作指引:去到工程目录下的 Config 文件夹下(我的是 EditorExtendTest\Config ),找到 DefaultEditorPerProjectUserSettings.ini 配置文件(如果没有,就创建它),在该文件中添加如下代码

[/Script/Blutility.EditorUtilitySubsystem]
StartupObjects=/Game/Editor/TestToolBar.TestToolBar

小 tips:如何快速得到这个路径 —— '/Game/Editor/TestToolBar.TestToolBar' ?找到我们创建的蓝图资源并右键选择 Copy Reference ,然后去配置文件中粘贴以 /Game/ 开头的部分,记得去掉单引号

重启工程,就可以看到工具栏已经有了我们刚才创建的工具栏按钮了

ok,工具栏按钮已经生成了,那么可以用蓝图的拖拖拖操作来给我们的按钮添加一些函数(自己的逻辑)

在蓝图编辑器中,将鼠标定位到左侧的 Functions 旁边的 Override 上,并点击 Execute 来编辑自己的逻辑,本例仅用了 PrintString 函数

3. 用 Python 进行编辑器扩展

到目前为止,UE4.26 版本提供了 Python3.7.7 版本的用于编辑器扩展的 Python 插件,这意味着我们可以在 UE4 中用 Python 扩展编辑器无需安装 Python

首先,我们需要激活这个插件

具体怎么用,请参考官方文档

使用 Python 脚本化运行编辑器

4. 用引擎自带的插件来扩展编辑器

注意,如果想使用这两个插件,那么一定要建一个 C++ 工程,基于蓝图的工程是没有的这个插件的

如果建的是基于蓝图的工程,那么结果如下:

用插件来扩展编辑器,不推荐,因为它会在c++工程中生成一个插件文件夹,大可不必!

完结!