正文
0. 前言
为了方便游戏开发,对编辑器扩展开发各种各样的工具是必不可少的,今天我们来看看在 UE4 中如何快速的进行编辑器扩展
1. 基于 C++ 的编辑器扩展
UE4 编辑器界面是基于 Slate 框架的,如果想要用 Slate 框架扩展编辑器,务必在你的模块构建文件 xxx.Build.cs 中添加 Slate 和 SlateCore 两个模块
如何自定义一个菜单栏、菜单、工具栏?
首先需要知道 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”,如何快速知道这些名字?
上面那些绿色的点就是可扩展点!
参数 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);
参数 MenuBarExtensionDelegate、MenuExtensionDelegate、ToolBarExtensionDelegate 为 单播委托,绑定一个回调函数,分别传入参数 FMenuBarBuilder、FMenuBuilder、FToolBarBuilder
这三个类都是继承于 FMultiBoxBuilder
知道了 FExtender 类,还需要知道 LevelEditorModule ,用它将我们添加的菜单,菜单栏、工具栏等进行管理,因此我们需要加载这个模块
// 加载 LevelEditor 模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
// 将菜单扩展性添加到 LevelEditorModule
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
上面说了一大堆,实际开发可不一定行!来个例子试一试:一个简单的多级菜单
来个完整的小白教程
- 创建一个 C++ 项目,名字 EditorExtendTest
- 修改 EditorExtendTest\Source\EditorExtendTest 文件夹下的 EditorExtendTest.Build.cs、 EditorExtendTest.h 和 EditorExtendTest.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 的代码为:
以 FExtender、 LevelEditorModule、 FMenuBuilder 为例
#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 Blueprint 和 Editor 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 属性,对,这就是我们要修改的内容
Menu:LevelEditor.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:插入指定扩展点位置的前后或者里面,枚举值(First、Before、After),我选的是 After
小 tips:经验证,这里不能修改,默认值就好(原因未知),否则会出现如下提示,并且按钮注册不了!
Advanced:高级选项
Tutorial Highlight:不知道干啥用,不管它,不填即可
Entry Type:按钮类型,主要有 MenuEntry、ToolBarButton 等等,我选的是 ToolBarButton
User Interface Action Type:响应类型,有 Button,Toggle 等,我选的是 Button
其他的不一一详述,因为有些参数我们并不关心,最后我的设置如下:
以上操作完成了 ToolBarButton 的创建,那么如何才能在我们的工具栏中看到这个按钮?
继续上面的步骤,我们需要添加 Run 自定义事件,用它来在 UE4 编辑器启动的时候注册这个按钮
去到 TestToolBar 蓝图的编辑界面,如果你打开的是如下界面,需要点击 Open Full Blueprint Editor
在蓝图编辑界面右键添加一个名为 Run 的自定义事件,按住鼠标拖动 Run 的执行引脚放开后找到 ToolMenus 下面的 Get 方法,然后拖动 Get 的 ReturnValue 执行引脚,找到 AddMenuEntryObject 方法
在蓝图编辑器空白处右键,创建一个 self ,并连接到 AddMenuEntryObject 的MenuEntryObject 位置
保存,编译
接下来,就要在配置文件中添加启动项了,详细内容可参考官方文档点这里
操作指引:去到工程目录下的 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
首先,我们需要激活这个插件
具体怎么用,请参考官方文档
4. 用引擎自带的插件来扩展编辑器
注意,如果想使用这两个插件,那么一定要建一个 C++ 工程,基于蓝图的工程是没有的这个插件的
如果建的是基于蓝图的工程,那么结果如下:
用插件来扩展编辑器,不推荐,因为它会在c++工程中生成一个插件文件夹,大可不必!