忆困血馆闻 发表于 2023-4-6 14:22

【UE5】ToolDev-1.工具开发基础

一:插件初识

环境:虚幻5.1 Rider2022.1.1
1.创建插件

UE的插件分为引擎插件和项目插件,引擎插件放在Engine/Plugins中,而项目插件放在项目目录下的Plugins中,引擎插件可以作用于所有项目但项目插件只能用于当前项目,引擎插件与项目插件除了存放的目录不相同外,其他差别不大。这里主要讨论项目插件。
首先在新建一个插件


可以看到如下的结构


2.uplugin

在插件目录最外层的是.uplugin文件。在虚幻引擎4启动的时候,会在Plugins目录里面搜索所有的.uplugin文件,每个.uplugin代表一个插件。
打开我们的ToolDev01.uplugin可以看到如下内容


其中比较重要的就是Modulees的配置,一个插件可以包含多个模块,如果要添加新的模块,只需要在Modules里面添加即可。其中的Type可以配置为以下类型

[*]Runtime 在任何情况下都会加载。
[*]RuntimeNoCommandlet Runtime模式但不包含命令。
[*]Developer 只在开发(Development)模式和编辑模式下加载,打包(Shipping)后不加载。
[*]Editor 只在编辑器启动时加载。
[*]EditorNoCommandlet Editor模式但不包含命令。
[*]Program 独立的应用程序。
而加载阶段(LoadingPhase)用于控制模块什么时候加载与启动,一般不需要配置,但有PreDefault(一般模块加载前)、PostConfigIni(虚幻引擎关键模块加载前)可选
3.Resources与Source

Resources目录,即资源目录,通常情况下其里面至少包含此插件的图标。
Source目录,即此插件的源码目录,通常情况下,在Source目录里,每个目录代表一个模块。如刚才创建的插件,在Source目录下就只有一个名为“ToolDev01”的目录,即“ToolDev01”模块(模块名与插件名可以相同)。.Build.cs为模块的配置文件(c#)
其中最常配置的是私有依赖PrivateDependencyModuleNames和公共PublicDependencyModuleNames
虚幻中的模块都是继承于IModuleInterface,其中有多个虚函数,其中StartupModule为模块入口
二:Slate基础

Slate是虚幻引擎中的自定义用户界面系统的名称。目前的编辑器中的界面大部分都是使用Slate来构建的。同时Slate也可以作为我们游戏开发时的UI框架(除了UMG以外又多了一项选择)。(详情见https://docs.unrealengine.com/5.1/zh-CN/understanding-the-slate-ui-architecture-in-unreal-engine/)
创建最简单的Slate界面的方法是创建一个带界面的插件。也就是我们最开始创建的StandaloneWindow。
在完成之后我们可以在window里面找到我们刚刚创建的插件。


1.初始化

回到我们的Rider中可以看到文件分为
ToolDev01(插件主类,插件入口与出口都在此类中。)
ToolDev01Commands(注册了一个OpenPluginWindow的命令。)
ToolDev01Style(定义UI样式的类)
在ToolDev01中可以看到StartupModule(),在其中依此完成了初始化工作,包括UI样式,注册命令,实例化命令绑定,菜单注册,注册Tab容器的操作。


而在这个菜单注册里面,又进行了菜单扩展和工具栏扩展的创建,并关联了我们的pluginCommands


在上面可以看到可以看到PluginCommands的执行函数为PluginButtonClicked,所以当点击按钮后最终就执行了PluginButtonClicked(函数)。
在PluginButton中又调用了FGlobalTabmanager的Invoke函数,在其中会启动我们之前初测好的Tab,调用最开始绑定Tab生成的处理函数OnSpawnPluginTab(),并在其内部生成具体的UI


2.UI创建

紧接着刚刚的生成UI代码,在Slate中创建一个新UI游两个方式SNew和SAssignNew,他们的区别在于SNew返回的是TSharePtr而SAssignNew返回TShareRef。
一般想要储存UI对象会在头文件总声明,并在cpp中使用刚刚提到的方法实例化,
例:TSharedPtr<SButton> TestButtonPtr;
SAssignNew(TestButtonPtr , SButton);
TestButtonPtr = SNew(SButton);
注意声明时候可以声明TSharedPtr也可以TSharedRef,但转换不是双向,TSharedRef可以通过AsShared函数直接转换为Ptr,反之则不行
3.控件

Slate中控件分为三种类型:
·Leaf Widgets 不带子槽的控件。如显示一块文本的STextBlock。
·Panels 子槽数量为动态的控件。如垂直排列任意数量子项,形成一些布局规则的SVerticalBox。
·Compound Widgets 子槽显式命名、数量固定的控件。如拥有一个名为Content的槽(包含按钮中所有控件)的SButton。
可以从默认文件宏看到Slate的绒里添加内容可以以中括号形式进行添加


对于中括号方式添加子控件,需要注意控件最终是要放到插槽里。在Slate系统里,容器并不是直接存储控件的,而是容器里的Slot(插槽)来存放控件。插槽的数量决定这个容器是属于什么类型:没有Slot的叫Leaf Widgets;Slot数量不固定的叫Panels;有明确的Slot的叫Compound Widgets 。
三:布局,控件与事件

1.自定义控件

自定义Slate控件需要继承SWidget 或其子类。同时,建议每种控件都有独立的头文件与源文件。
我们首先创建两个文件


内容非常简单,其中SLATE_BEGIN_ARGS和SLATE_END_ARGS为固定的控件格式。Construct函数为控件构造函数,会在实例化此控件的时候执行。SCompoundWidget自带一个名叫ChildSlot的子槽。我们如果要添加控件,需要添加到ChildSlot里。这里我们在ChildSlot添加了一个按钮(SButton)。


为了让我们能看到效果我们回来ToolDev01.cpp中,加入引用#include "WidgetTestA.h"并且修改一下OnSpawnPluginTab()函数


之后重新编译启动一下,重新打开我们的插件窗口,可以看到一个巨大的按钮占满了整个屏幕


2.UI布局

UI布局通常使用Panels控件。常用的有SVerticalBox、SHorizontalBox、SOverlay 。
在对VerticalBox添加新的子插槽只需要+SVerticalBox::Slot()即可
一个简单的布局如下,使用了垂直和水平嵌套


最终效果如下,就是几个按钮的排列


3.控件参数与属性

每个控件都有自己的属性与参数,参数的定义会用到SLATE_ATTRIBUTE宏,并且必须放在必须放在SLATE_BEGIN_ARGS和SLATE_END_ARGS中间。我们在刚刚创建的.h中添加一个FString属性,名叫InText


之后便可以在CPP中通过够惨函数的InArgs参数获取InText的值,并打印到屏幕
在Slate中传递的参数,属性,代理都会放到构造的InArgs里。


之后便可以到ToolDev01.cpp中,添加属性


编译启动后,当我们打开窗口的瞬间就可以看到屏幕左上角出现字符


4.Delegate

控件除了显示之外还需要与它进行交互。例如在刚刚的例子中,我们可以在.h文件中添加


在Slate中,处理反馈函数都要返回FReply,告诉系统已经处理了这个事件
接着到.cpp中实现方法,并且写回调函数


当我们点击绑定的上面那个大按钮就可以看到


对于刚刚的回调函数,可以看到里面有一个宏,Slate的宏一般遵守(类型名,变量名)的规则,Slate_event用于事件反馈


然后为了测试Event我们新建一个类


其中.h内容如下,定义了一个代理指明参数,并且暴露一个接口传入回调方便,还有一个变量储存回调方法


.cpp内容如下,进行回调函数的存储,界面的创建,以及点击后的函数实现


最后将其添加到我们一开始的Widget中
在.h中加入OnLogin方法


在cpp中实现,并且添加引用和回调


编译后运行可以看到最终效果如下


四:Style

1.定义与使用样式

一般而言,资源都放在Resource目录下
这里除了默认资源外准备了几个图和一个字体


要添加新的样式只需要在ToolDev01.cpp的Create函数添加相应代码即可


在上述过程中我们只是创建了样式,但还并未使用
创建一个新的皮肤样式使用Stype->Set的方式添加。其中第一个参数为样式的名称,第二个参数指定具体的样式。同时还有IMAGE_BRUSH宏创建图片
在设置完之后我们就可以使用他们,来到Event的cpp中,引用样式文件后就可以开始使用


这样编译启动后,可以看到我们的图标生效了(有点点小)


界面按钮,图片也是正常的,除了这个字体


这个Bug是因为我们没有读取到字体,我们是直接从引擎和Context里面去读,肯定找不到。所以字体需要复制一份到Context之中,也可以修改一下写法(这里采取修改秀发)重新编译后便正常了




2.图标字体

虚幻图标字体使用的是Font Awesome,图标字体可能在没有设计师的配合或者没精力设计图标的情况下,用代码快速生成漂亮的图标。
想要使用可以在Build.cs中PrivateDependencyModuleNames中添加“EditorStyle”模块引用


之后再需要使用的地方引用头文件并设置字体与内容


编译之后就可以看到我们的Book和Car


对于这里图标可以进到EditorFontGlyphs.h中查看
使用组件继承的方式,可以有效降低代码量,让代码结构更清晰。同时,继承也大量用于扩展已有组件的功能。我们下面就可以用组件继承方式实现字体图标
首先我们创建两个文件


在头文件中继承SButton并且暴露两个属性接口


在cpp中去实现功能,传入两个FText参数就可以创建带小图标的按钮,在其中调用了父类的构造函数


之后仍然来到我们的SWidgetTestA中调用MyButton


编译后可以达到我们的预期效果


3.动态控制Slot

在之前的使用中我们已经设置过Slot的长宽,位置,外边距等,但这些都是写死的,如果想要改变就可以用类似SAssignNew的方法-Slot的Expose函数。
首先来到头文件中,定义一个Slot指针和一个一会儿用到的方法


之后在创建Slot的时候就可以通过Expose方法把Slot指针赋值过来,之后我们再添加四个按键用于选择放置样式




编译运行后最终效果如下


4.自定义容器布局

在之前我们用到的基本都是SCompoundWidge组件,但还有Panel类型组件,比如SVerticalBox就是垂直布局组件。
在Slate中,容器布局是靠重写OnArrangeChildren函数来完成的。
下面我们来完成一个自适应窗口宽度排序的容器组件
同样先新建文件


在头文件中定义了一个ContentMargin控制元素之间间隙,还有一个AddSlot动态向容器中添加子元素,并overide一个当容器变化时候的函数。(此处AddSlot直接抄的可能不太正确但先用着)


来到cpp中,构造函数取出元素数量,因为在外面我们会用AddSlot动态添加


之后来到我们的关键函数,大致逻辑为获取容器尺寸后,从左到右依次排,排到排不下就把位置设置到下雨一行开头


最后来到WidgetTestA中使用


编译启动后效果如下


参考资料:

大象无形虚幻引擎程序设计浅析
页: [1]
查看完整版本: 【UE5】ToolDev-1.工具开发基础