UE5编辑器Slate快速入门【开篇】

创建编辑器工程

可以把搭建编辑器看成一个更加高级的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::Type​docs.unrealengine.com/4.26/en-US/API/Runtime/Projects/EHostType__Type/

附2:LoadingPhase枚举值:

ELoadingPhase::Type​docs.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的子类主要有三种,分别是SCompoudWidgetSLeafWidgetSPanel ,我们主要基于这三个类来构建我们的控件。

他们三个最主要的区别在于能附加子控件的数目。

  • SCompoudWidget
    其子类只能拥有一个子控件,常见的子类有SButton,SBorder等,他们的特点都是只能附加一个子控件
  • SLeafWidget
    其子类已经是叶子节点,不能再拥有子控件,常见的子类有SImageSTextBlock,这类控件都是没有子控件插槽
  • 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;来存放值,并在构造函数中为其赋值。

ChildSlotSCompoundWidget子类所拥有的唯一一个插槽,我们可以在其中放置我们所需要的控件类型。

#include "NewWidget.h"
​
void NewWidget::Construct(const FArguments& InArgs)
{
    IsFocusable = InArgs._IsFocusable ;
    
    //插槽中创建一个按钮
    ChildSlot[
        SNew(SButton)
    ];
​
}
2 使用自定义控件

使用自定义控件和使用基础控件的方法其实是一样的,就是使用SNewSAssignNew来进行创建

我们在开始创建的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实现可以参考链接:

设计模式--MVC(C++版) - 孟栋sky - 博客园​www.cnblogs.com/mengdongsky/p/6825856.html?utm_source=itdadao&utm_medium=referral

这里我们可以简单点,就理解为数据读取、业务逻辑、UI界面三者的分离。

其中最值得关心的部分是业务逻辑和UI如何分离?这里就可以很巧妙的运用代理来解决。

大致逻辑如下:

  • 创建一个单例类Controller,由其来SNew我们需要的UI,并对外提供接口(这样就可以让Controller包含所有UI的指针引用)。
  • UI只提供代理,Controller通过获取UI的指针来获取其代理来绑定函数,这样就只有单向的包含,UI和Controller无关。
  • 主要业务逻辑都在Controller中实现

2 学习引擎源码

学习UE4编辑器最好的教程就是该引擎本身,UE4引擎本身就是由Slate来进行创建渲染的,而且引擎本身开源,我们可以借助引擎的源码来学习UE4是如何运用Slate框架来创造其编辑器界面的。通过小番茄助手和Rider,我们可以很快捷的找到引擎中源码的使用

【划重点!!!】此外,UE4引擎中也针对Slate编程提供了一个非常详细的案例工程代码SlateViewer,很有学习价值(代码有点小难度),并且可以直接运行。这个工程在源码引擎中,需要读者下载源码引擎。

无需启动引擎,即可单独启动该程序,启动后效果如下:

【这里有位老哥写得挺好,我就引用他截的图,不再重复做了,具体可以看下列链接和图片】

【下列图片来源链接:

https://img-blog.csdnimg.cn/20210611134748511.gif#pic_center​img-blog.csdnimg.cn/20210611134748511.gif#pic_center

超好用工具:控件反射器

为了方便我们查看Ue4引擎各个部分界面是如何构成的,UE4还提供了一个控件反射器,在工具栏中。

通过使用该工具,我们甚至还可以直接定位到对应UI部分的源码,从而方便我们学习

主要参考链接

官网Slate介绍:

https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/Slate/​docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/Slate/

插件和模块:

胡嘿嘿:【UE4】插件与模块91 赞同 · 8 评论文章

Slate框架:

锅约科:UE4 Slate基本框架<0>27 赞同 · 0 评论文章正在上传…重新上传取消

MVC框架:

设计模式--MVC(C++版) - 孟栋sky - 博客园​www.cnblogs.com/mengdongsky/p/6825856.html?utm_source=itdadao&utm_medium=referral

UE4代理:

UE4代理(委托)总结 | CloudBoy​cloboy.gitee.io/2020/11/14/ue4-dai-li-gai-lan/#toc-heading-6正在上传…重新上传取消

猜你喜欢

转载自blog.csdn.net/ale26/article/details/126000731