创建编辑器工程
可以把搭建编辑器看成一个更加高级的UMG,让你做 更多的定制化,并能够释放C++的威力,因为你要知道UE4的界面就是用这玩意写的。虽然有点难,但是都有迹可循,不慌!
1.1 创建工程
UE4编辑器直接通过插件即可创建,为了能够使用Slate,我们选择Editor Standalone Window类型的插件,这种插件包含默认的Slate的框架,填写好Author和Description选择Create Plugin,这里我们创建一个名为CloudBoy的项目
1.2 配置文件.uplugin(基本不用动,可跳过)
创建完打开项目,我们可以看到该插件工程目录下的配置文件.uplugin,见下图,主要作用是配置这个编辑器插件的运行状态和类型,后续根据具体需求来进行配置即可,开始默认就行,问题不大。
其中Type和LoadingPhase的具体枚举值可在官网查看内容,这里我们一般用RunTime和Editor两种类型
附1:Type枚举值:
EHostType::Typedocs.unrealengine.com/4.26/en-US/API/Runtime/Projects/EHostType__Type/
附2:LoadingPhase枚举值:
ELoadingPhase::Typedocs.unrealengine.com/4.26/en-US/API/Runtime/Projects/ELoadingPhase__Type/
1.3 项目文件结构介绍
Slate插件项目中,UE4为我们默认创建了三个CPP文件,其目录结构如下图所示
- CloudBoy.cpp:插件模块主干程序
- CloudBoyCommands.cpp:相关命令配置(设置插件
打开按钮映射
等) - CloudBoyStyle.cpp:Slate控件自定义风格样式设置(Brush),其实就是UMG的Brush类型设置
上面三个文件,CloudBoy.cpp是最主要的,我们的插件的启动逻辑都写在里面!然后我们传入我们样式的地方也在这里(在OnSpawnPluginTab
函数中)
上图红色框着的地方就是我们的写编辑器的起点,可以把SNew(Sbox)部分删除掉,来写自己的内容。
1.4 易混淆概念部分
插件名和模块名:以这个插件为例,这个插件名为CloudBoy,同时该插件包含一个同名模块也叫做CloudBoy,可以在.build.cs文件中看到。因为UE4中一个插件至少由一个模块组成,因此这里是UE4为我们默认创建一个同名的模块,我们实际编辑的是这个模块。
二、使用Slate基础控件
2.0 认识链式编程
链式编程的形式如下面所示,通过SNew一个控件类型,然后通过.xxx
来配置参数或者是绑定相关事件,然后再通过中括号包含其内容。
小提示:最外层的括号结尾记得加分号!
其实这个链式编程流程和UMG的层级插槽是一致的,可以参考下面这张图
2.1 创建Button实例
下面我们快速自己创建一个按钮来实践下这个流程,先把原有内容Sbox删除
在链式编程中创建控件实例其实我们有SNew和SAssignNew两种方法,都可以通过其获得控件实例指针,但是SNew不强制赋值,SAssignNew必须提供类型指针
好了,我们开始创建Button,实现点击Button打印字符串,代码如下,关键代码在红色框选部分
在上图中,我们既配置了按钮上显示的字符,也配置了按钮点击时绑定的事件,我们编译后在UE4工具栏查看我们的插件,可以看到插件页面就有了一个大大的按钮。可以进行点击,然后会打印日志,说明我们的插件生效了
对于其他类型的基础控件
,我们也可以举一反三,其实就和使用UMG时的方法是一样的,毕竟UMG就是基于Slate来进行创建的。
三、如何创建自定义控件
3.0 控件基类
UE4 Slate框架中最基础的类是SWidget
,基于SWidget
的子类主要有三种,分别是SCompoudWidget
、SLeafWidget
、SPanel
,我们主要基于这三个类来构建我们的控件。
他们三个最主要的区别在于能附加子控件的数目。
SCompoudWidget
其子类只能拥有一个子控件,常见的子类有SButton
,SBorder
等,他们的特点都是只能附加一个子控件
SLeafWidget
其子类已经是叶子节点,不能再拥有子控件,常见的子类有SImage
、STextBlock
,这类控件都是没有子控件插槽
的SPanel
其子类的特点是可以无限添加子控件,没有数量限制,常见的子类的有SHorizontal(水平框)
、SPanel
等等
详细介绍可见链接
:
锅约科:UE4 Slate基本框架<1>11 赞同 · 0 评论文章正在上传…重新上传取消
基于上面三种基类,UE4创建了许多基础样式为我们所用,我们也同样可以基于上述三种基类
来创建我们自己的样式。
3.1 创建自定义控件
这里我们以创建一个继承于SCompoudWidget
的子类为例
// NewWdiget.h
#pragma once
class NewWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(NewWidget) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
};
// NewWdiget.cpp
#include "NewWidget.h"
void NewWidget::Construct(const FArguments& InArgs)
{
ChildSlot[
SNew(SButton)
];
}
上面的代码就完成了一个非常简单的自定义控件的创建,我们只为其插槽加了一个按钮。(这其实是一个非常好的模板,按照这个模板来创建你的其他自定义控件)
不同于其他类,基于SCompoundWidget
的子类,有一段SLATE_BEGIN_ARGS(NewWidget) {} SLATE_END_ARGS()
的宏,这里的宏是来让我们自定义控件中的参数、事件、插槽等等;而且还有一个属于Swidget专属的构造函数void Construct(const FArguments& InArgs);
1 声明自定义参数
在上述宏中我们可以用SLATE_ATTRIBUTE(属性)、SLATE_EVENT(事件)、SLATE_ARGUMENT(参数)、SLATE_NAMED_SLOT(插槽) 和 SLATE_DEFAULT_SLOT来声明我们的需要的参数。这里详细可以参考链接:
锅约科:UE4 Slate基本框架<0>27 赞同 · 0 评论文章正在上传…重新上传取消
中的内容。
这里我们以常用的SLATE_ARGUMENT(参数)
为例
#pragma once
class NewWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( NewWidget )
: _IsFocusable( false )
{}
SLATE_ARGUMENT( bool, IsFocusable )
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
private:
bool IsFocusable;
};
例如上面我们定义了IsFocusable
的参数,并对他进行了初始化_IsFocusable(false)
通过宏定义的参数其实会变为_
+参数名
的形式,并存放在由宏定义的结构体 Arguments
中。
Arguments
这个结构体主要是为了在控件构造创建时方便
将自定义参数的值传递
给当前我们定义的类中的同名变量
,因此我们还需要在类中创建一个同名成员变量bool IsFocusable;
来存放值,并在构造函数中为其赋值。
ChildSlot
是SCompoundWidget
子类所拥有的唯一一个插槽,我们可以在其中放置我们所需要的控件类型。
#include "NewWidget.h"
void NewWidget::Construct(const FArguments& InArgs)
{
IsFocusable = InArgs._IsFocusable ;
//插槽中创建一个按钮
ChildSlot[
SNew(SButton)
];
}
2 使用自定义控件
使用自定义控件和使用基础控件的方法其实是一样的,就是使用SNew
和SAssignNew
来进行创建
我们在开始创建的CloudBoy
插件中的OnSpawnPluginTab
函数中创建上述我们自定义的控件,并初始化其自定义参数
#include "NewWidget.h" //首先引入头文件
TSharedRef<SDockTab> FCloudBoyModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
FText WidgetText = FText::Format(
LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
FText::FromString(TEXT("FCloudBoyModule::OnSpawnPluginTab")),
FText::FromString(TEXT("CloudBoy.cpp"))
);
TSharedPtr<NewWidget> testWdiget; //定义指针
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SAssignNew(testWdiget, NewWidget) //创建自定义控件
.IsFocusable(true) //初始化自定义参数
];
}
因为自定义控件中放的是一个按钮,因此结果插件显示的效果也是一个大的白色按钮
------------------------------------------------------------------------------------------------
.2 代理/事件使用
在使用Slate写UI界面时,因为UI交互的复杂性,我们经常需要通过代理和事件
来帮助在UI界面和操控之间进行解耦或绑定响应函数,这是写UI界面很重要的一部分。
Slate中去定义代理和事件也是用的UE4实现那一套代理声明逻辑(单播,多播,事件,动态单多播),只是会多一些步骤。
UE4代理部分可以参考:
云上男孩:UE4代理(委托)概览16 赞同 · 7 评论文章正在上传…重新上传取消
Slate中也提供了一些提前声明好的基础常用事件
,可以直接拿来用,例如FOnClicked
,定义在SlateDelegates.h
文件中,可以直接拿来用。这里我们可以参考官方的FOnClicked
来实现一个自己的事件。
//声明带返回值代理类型
DECLARE_DELEGATE_RetVal( FReply, FOnMyClicked )
//声明代理变量
class NewWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( NewWidget )
: _IsFocusable( false )
{}
SLATE_ARGUMENT( bool, IsFocusable )
SLATE_EVENT(FOnMyClicked, OnMyClicked) //在FArguments中声明
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
private:
bool IsFocusable;
//声明代理成员变量
FOnMyClicked OnMyClicked;
};
//为成员变量赋值
void NewWidget::Construct(const FArguments& InArgs)
{
IsFocusable = InArgs._IsFocusable ;
OnMyClicked = InArgs._OnMyClicked ;
//插槽中创建一个按钮
ChildSlot[
SNew(SButton,OnClicked())
.OnClicked(FOnClicked::CreateRaw(this, &NewWidget::OnClickNowButton)) // 按钮绑定函数
];
}
重点:代理的使用
使用代理主要是两个步骤,一个是执行代理,一个是绑定响应函数(注意FOnMyClicked
仅仅只是一个代理,并不包含检测按键按下的功能,在Sbutton
控件中之所以能响应按键,是因为OnClicked
被能够检测按键响应的函数调用)
- 执行代理
这里我们创建的按钮的按下操作函数来执行代理
,通过Execute()
来直接执行
FReply NewWidget::OnClickNowButton()
{
if(OnMyClicked.IsBound())
{
FReply Reply = OnMyClicked.Execute();
}
return FReply::Handled();
}
- 绑定响应函数
// OnSpawnPluginTab函数
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SAssignNew(testWdiget, NewWidget)
.IsFocusable(true)
.OnMyClicked(FOnMyClicked::CreateRaw(this, &FCloudBoyModule::OnClickNowButton))
];
// 绑定的函数
FReply FCloudBoyModule::OnClickNowButton()
{
UE_LOG(LogTemp, Log, TEXT("Bind Fcuntion!"));
return FReply::Handled();
}
绑定好后,运行程序,点击按钮便可以成功打印日志
对于代理的使用,我们需要多加熟悉,对于程序解耦、UI交互有很大的帮助,后面也会讲述到。
四、编程/框架建议
1 运用设计模式
对于Slate进行编程时,如果把逻辑和写UI的链式编程写在一起,我们会发现一个程序会写得非常长,而且UI界面和业务逻辑耦合特别多,很难拆分,这里建议使用一些常见的设计模式来方便我们的程序开发,提升程序的复用性,健壮性,这里推荐MVC框架
这种设计模式
MVC框架中的字母分别是:M是指业务模型,V是指用户界面,C则是控制器,也就是Model、View、Controller,通过这种框架的方式,我们可以很好的实现业务逻辑、数据、和UI界面的解耦。纯C++代码的MVC实现可以参考链接:
这里我们可以简单点,就理解为数据读取、业务逻辑、UI界面三者
的分离。
其中最值得关心的部分是业务逻辑和UI
如何分离?这里就可以很巧妙的运用代理
来解决。
大致逻辑如下:
- 创建一个单例类
Controller
,由其来SNew
我们需要的UI,并对外提供接口(这样就可以让Controller包含所有UI的指针引用)。 - UI只提供代理,
Controller
通过获取UI的指针来获取其代理来绑定函数,这样就只有单向的包含,UI和Controller无关。 - 主要业务逻辑都在
Controller
中实现
2 学习引擎源码
学习UE4编辑器最好的教程
就是该引擎本身
,UE4引擎本身就是由Slate来进行创建渲染的,而且引擎本身开源,我们可以借助引擎的源码来学习UE4是如何运用Slate框架来创造其编辑器界面的。通过小番茄助手和Rider,我们可以很快捷的找到引擎中源码的使用
【划重点!!!】此外,UE4引擎中也针对Slate编程
提供了一个非常详细
的案例工程代码SlateViewer
,很有学习价值(代码有点小难度),并且可以直接运行。这个工程在源码引擎中,需要读者下载源码引擎。
无需启动引擎,即可单独启动该程序,启动后效果如下:
【这里有位老哥写得挺好,我就引用他截的图,不再重复做了,具体可以看下列链接和图片】
【下列图片来源链接:
】
超好用工具:控件反射器
为了方便我们查看Ue4引擎各个部分界面是如何构成的,UE4还提供了一个控件反射器,在工具栏中。
通过使用该工具,我们甚至还可以直接定位到对应UI部分的源码,从而方便我们学习
主要参考链接
官网Slate介绍:
插件和模块:
Slate框架:
锅约科:UE4 Slate基本框架<0>27 赞同 · 0 评论文章正在上传…重新上传取消
MVC框架:
UE4代理:
UE4代理(委托)总结 | CloudBoycloboy.gitee.io/2020/11/14/ue4-dai-li-gai-lan/#toc-heading-6正在上传…重新上传取消