通过在项目源文件夹下添加Editor Module可以对项目进行定制化的引擎修改,非常有意思。
准备工作
首先我们需要有一个项目,同时也需要一个IDE来管理我们的项目,创建一个虚幻的C++项目网上很多教程,这里就不再赘述。IDE我选用的是Rider,这里必须安利一句Rider对于虚幻真的是非常的好用,个人感觉要比VS+番茄好用的多。同时该有的分析功能Rider也都有,JetBrain必然改变你的生活。
创建一个新的Editor Module
在Rider的项目中寻找到Game→Source下创建一个Unreal Module
一般而言创建Editor Module的命名规范是ProjectName + Editor,一个项目中一般仅有这一个Module为修改编辑器而服务,这里我的项目命名为Example。注意Module类型为Editor,加载的阶段为引擎初始化之后加载。
完成后会新建以下文件夹,等待unreal generating system重新生成uproject file即可使用。
当然,你也可以手动创建文件夹和cs文件,然后手动重新生成uproject file,新的uproject file 应当包含新的模组信息:
Editor Module的代码结构
Editor module与Runtime Module的Target.cs文件格式并没有什么不同,唯一的不同就是Type的种类有所不一样。
该文件主要是传给UBT(虚幻Bulid Tools)用的,目的是让它知道这个Module叫啥,干啥,用于哪个版本的。
接下来是Editor Module 的Build.cs文件,主要是添加该项目的依赖
随后让我们看一下Module的类
这里的Module文件继承了IModuleInterface并重写了两个方法。StartupModule在Module编译出的DLL文件加载之后之后立即调用,而ShutdownModule在卸载Module之前调用。
在定义中我们会发现这两个函数是两个空的定义,并且定义在一个本地的namespace中:
在cpp文件的最后,IMPLEMENT_MODULE向引擎声明了这个Module,这回为引擎返回一个该Module的实例,以便调用该Module内的内容。
示例:一个按钮
接下来我们会实现一个在引擎编辑器中可供点击的按钮以便更好地理解编辑器模组。
1 定义TCommand类
首先第一步,我们需要定义一个TCommands类,该类是命令模式的具体实现,其目的是保存所有自定义的命令的引用,而使用该类的子类可以让我们重写该类中的具体函数。
在Editor Module的Public文件夹中新建一个C++类(并非Unreal C++类),这里我命名为ButtonCommands.h。
基于虚幻的命名规范,我们在文件中将其重命名以F开头,并公有继承TCommands
为这个类写一个构造函数,不需要具体的实现,直接用初始化表调用父类构造函数
该函数有四个参数,名字,描述,父类,StyleSetName:
名字和描述自己决定即可,这里的父类表示,如果该类继承自其他继承了TCommand的类,我们需要在这里添加该类的名称,这个名称是另一个Command在相同的构造函数中定义的。最后的Style是虚幻的Slate(简单理解为编辑器里的Canvas,意为石板) Style集,默认的话传入AppStyle的Name即可,这能让我们使用虚幻引擎里自带所有的图表和格式,甚至还包括老版本的图标。
有了这个构造函数,类定义就不会跳黄了。
接下来,我们重写一个叫做RegisterCommands的函数,该函数将负责创建命令,同时我们创建一个Shared Pointer 以保存我们按钮将要执行的命令。
接下来让我们看cpp文件,首先根据规范,我们最好将cpp文件放入Private文件夹以便对其他模组隐藏,之后定义该复写的函数RegisterCommands。
由于我们只有一个命令,所以这里我们仅仅使用UI_COMMAND创建一个命令。
该宏的第一个参数为保存命令的指针,该宏会将生成的命令的地址赋予这个指针,随后就是该命令的名称和描述,这里不需要传入FName和FText。第四个参数是该命令在UI上调用行动的类型,是按钮还是下拉菜单,其功能不一样实现自然也不一样。
最后一个是该命令的快捷键组合FInputChord(直译为输入和弦,虚幻的类命名真的是太骚了…),由于我们不需要快捷键,直接创建一个实例会创建一个空的快捷键组合传进去。如果有需求可以定义好输入快捷键。
至此,我们已经创建了这一个命令,接下来我们将告诉引擎这个命令应当显示在哪里,应当干什么。
2 注册命令到引擎
为了完成注册,或者简单地说添加该命令到引擎的这一操作,我们需要对一开始创建的Module类中进行配置。虚幻UBT不会检测我们自己配置的类,但是却会调用每个Module的Startup函数。
首先在StartupModule中,首先调用注册命令的函数。
该函数是一个静态函数所以可以直接调用,该函数会运行Commands实例中我们之前定义的注册函数。
接下来,我们需要向Module声明引擎的拓展,同时定义我们所需的函数,在Edit Module的头文件中声明以下两个指针和以下两个函数,之所以需要将这两个指针赋出来,是因为我们需要在Shutdown中将他们删除掉:
接下来在Startup函数中,我们定义一个CommandList,这个实例会将我们的按钮命令信息FUICommandInfo和函数关联起来。
这里的MapAction函数负责关联CommandInfo和需要真正执行内容。
第一个参数为CommandInfo,第二个参数我们通过CreateRaw创建了一个C++原生的函数代理(函数指针的指针)最后一个代理是询问该命令能否执行的一个判断,这里的FCanExecuteAction会永远返回可以执行(True)。
随后让我们将这个CommandInfo添加到编辑器。
3 添加CommandInfo到编辑器
在这一步之前,我们先需要获得编辑器的各个拓展的位置信息:
重新启动引擎,我们可以看到以下信息
我会希望我的新的拓展在Play的右边,所以在添加的时候使用EExtensionHook::After来确定我的位置。
最后,将这个Extender添加到关卡模块的管理单例即可:
随后在Shutdown中我们移除这个Extension并将指针设为空: