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

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

本节将对Slate插件工程进行一个详解,还是本着不放过每个细节、不模棱两可的态度,对插件工程的方方面面进行洞悉,期待和大家一起交流。

前言

Slate是UE4自带的一套升级版IMGUI框架,既能用于Runtime中的UI,也能用于Edit状态下的操作界面创建,其强大的功能能满足你各种复杂的需求。

UE4本身的编辑器界面是由Slate框架进行创建的,包括我们用于UMG的widget也是基于Slate进行封装,因此我们也同样可以借助这套工具来帮助策划定制一套集成于UE4的编辑插件。对这样一套神奇的框架,我们已经迫不及待对其进行学习和运用,那就开始吧!

附1:官方Slate介绍:docs.unrealengine.com/4

一、基础工程介绍/回顾

UE内为我们准备好了包含Slate框架基础插件程序,我们可以直接创建来看看究竟。

1.1 工程创建

在【开篇】我们已经介绍过,这里简单过一遍。

确保游戏项目为C++项目,如果为蓝图项目,需要创建C++工程

点击面板中Settings下的Plugins进行创建(菜单栏的Edit下也有同样目录)



在插件界面右下绿色按钮选择创建的插件模板,使用Slate需要创建带Editor字样的的插件,这里我们选择Editor Standalone Window类型。填好名字(SlateTest),作者,就可以用打开项目代码了。



1.2 工程概览

打开创建好的程序,可见其目录结构如下:


编辑器插件目录结构介绍:
  • Resources(文件夹)
    • .png :该图片作为Slate插件在引擎中图标按钮
  • Souce(文件夹)
    • SlateTest(模块名称)
      • Private
        • SlateTest.cpp:插件模块主程序
        • SlateTestCommands.cpp:用于按键的映射设置(可为按钮绑定键盘快捷键
        • StateTestStyle.cpp:Slate控件自定义风格样式设置(Brush
      • Public
        • 同上
      • .Build.cs文件:每个模块对应一个“ModuleName.Build.cs”,主要是用于配置第三方库,让UE程序能够正确调用库(因为UE4使用自己的UBT来编译,利用C#来处理程序依赖)
        附2:理解UE4静态库和动态库的使用:cnblogs.com/sevenyuan/p
    • uplugin文件:插件配置(描述)文件,用于设置插件包含的模块和运行方式
      附3:插件官方介绍:docs.unrealengine.com/4


Tips:不知道伙伴们有没有发现,程序目录中有两个命名和你创建插件同名的目录,例如上面,一个SlateTest目录下包含了另一个SlateTest目录,其实第一个SlateTest是插件的目录,第二个是SlateTest是模块的目录,因为一个插件至少含有一个模块的原则,所以UE其实是为我们创建了一个同名的模块。

二、主程序介绍

Slate插件主程序其结构如下

SlateTest模块的代码


SlateTest继承于IModuleInterface基类,因此它是一个模块类,可以见到插件模块里面主要有五个函数,他们实现了slate模块的基本结构

+StartupMoudule():模块开始执行的方法

+ShutdownModule():模块注销执行的方法

+PluginButtonClicked():插件入口的按钮触发事件(启动模块函数中绑定的按键事件)

+RegisterMenus():将模块注册到UE4编辑器菜单栏和工具栏中(让插件可在主菜单显示)

+OnSpawnPluginTab():Slate插件的可视化程序部分,也是写Slate插件最主要编写的部分

2.1 StartupModule函数

void FSlateTestModule::StartupModule()
{
    // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
    
    /* 初始化SlateStyle样式,加载图标等 */
    FSlateTestStyle::Initialize();
    FSlateTestStyle::ReloadTextures();
    
    /* 注册Commmand管理器,映射绑定输入指令和操作功能 */
    FSlateTestCommands::Register();
    PluginCommands = MakeShareable(new FUICommandList);
    PluginCommands->MapAction(
        FSlateTestCommands::Get().OpenPluginWindow,
        FExecuteAction::CreateRaw(this, &FSlateTestModule::PluginButtonClicked),
        FCanExecuteAction());
    
    /* 为模块注册在菜单栏和工具栏上的入口 */
    UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FSlateTestModule::RegisterMenus));
    
    /* 为该模块注册一个Tab显示窗口 */
    FGlobalTabmanager::Get()->RegisterNomadTabSpawner(SlateTestTabName, FOnSpawnTab::CreateRaw(this, &FSlateTestModule::OnSpawnPluginTab))
        .SetDisplayName(LOCTEXT("FSlateTestTabTitle", "SlateTest"))
        .SetMenuType(ETabSpawnerMenuType::Hidden);
}


void FSlateTestModule::PluginButtonClicked()
{   
    /* 触发注册在TabManager中的Tab窗口 */
    FGlobalTabmanager::Get()->TryInvokeTab(SlateTestTabName);
}

2.2 OnSpawnPluginTab()函数

该函数用于生成显示插件模块的Tab主界面,采用链式编程。

/* 该函数为Slate的Tab主界面的生成函数  */ 
TSharedRef<SDockTab> FSlateDemoOneModule::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("FSlateDemoOneModule::OnSpawnPluginTab")),
        FText::FromString(TEXT("SlateDemoOne.cpp"))
        );
    
    /* 链式编程构建界面 */
    return SNew(SDockTab)
        .TabRole(ETabRole::NomadTab)
        [
            // Put your tab content here!
            SNew(SBox)
            .HAlign(HAlign_Center)
            .VAlign(VAlign_Center)
            [
                SNew(STextBlock)
                .Text(WidgetText)
            ]
        ];
}

2.3 RegisterMenus()函数

该函数用于创建该模块在UE编辑器上的菜单入口(包括上菜单栏和中间工具栏入口)

Tips:在UE5和UE4的工具栏的可拓展插槽位置略有不同,UE5【LevelEditor.LevelEditorToolBar.PlayToolBar】, UE4 【LevelEditor.LevelEditorToolBar】

/* 在编辑器菜单栏和工具栏拓展按钮 */
void FSlateDemoOneModule::RegisterMenus()
{
    // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
    FToolMenuOwnerScoped OwnerScoped(this);

    {   
        /*  主菜单栏中插入触发按钮  */
        UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
        {   
            //在WindowLayout位置片区添加触发按钮
            FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
            Section.AddMenuEntryWithCommandList(FSlateDemoOneCommands::Get().OpenPluginWindow, PluginCommands);
        }
    }

    {
        /*  主工具栏中插入触发按钮  */  
        UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
        {
            // 在工具栏的Settings片区中添加触发按钮
            FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
            {
                FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FSlateDemoOneCommands::Get().OpenPluginWindow));
                Entry.SetCommandList(PluginCommands);
            }
        }
    }
}

2.4 模块的注册

这一节看后续文章详细讲解

2.5 总结

通过上述代码的分析,我们可以知道一个slate plugin创建和打开的流程


三、Commands.cpp文件介绍

Commands.cpp涉及到编辑器的命令管理,整个插件主要涉及到三个类FUICommandInfo、TCommands、FUICommandList

【1】FUICommandInfo是创建和管理编辑器命令的类,编辑器命令是指在编辑器上执行的操作:打开一个窗口、保存一个文件等。我们可以将每个操作抽象为一个该对象,实现界面与具体操作的解耦,也可以实现多个操作对应一个命令(例如Ui按钮和快捷键对应同一个命令操作) 【2】TCommands可以理解为FUICommnadInfo的容器,方便插件统一定义和初始化相关FUICommnadInfo【您原来就是一个config文件】,这也是创建commands.cpp的原因。

【3】FUICommandList可以理解为命令的管理器,用于创建、注册和管理命令。通过FUICommandList,我们可以将FUICommandInfo与UI控件触发事件绑定在一起。

4.1 流程图介绍

插件中命令的具体流程如下(从上往下看)



具体代码流程总结就是三步:定义命令(初始化)、绑定命令与执行事件(执行)、绑定命令与具体控件UI(输入)

4.2 代码流程

结合上述概览的command流程,我们来看看具体代码吧!

步骤一、定义UI命令(Commands文件中)

Commands文件主要用于配置UICommandInfo,从而设置UICommandInfo相应的信息、样式、快捷键功能等。

// Commands.h文件
class FSlateTestCommands : public TCommands<FSlateTestCommands>
{
    /* .....................................  */
public:
    TSharedPtr< FUICommandInfo > OpenPluginWindow;
};


// Commands.cpp 文件
void FSlateTestCommands::RegisterCommands()
{
    UI_COMMAND(OpenPluginWindow, "SlateTest", "Bring up SlateTest window", EUserInterfaceActionType::ToggleButton, FInputChord());
}
小问环节:既然TCommands可以理解为一个配置文件,那么是不是我们可以不在TCommands文件中定义呢?
答:当然,但是TCommands提供了方便创建FUICommandInfo的宏,如UI_COMMAND(),除此之外,还提供了其对应的生命周期的统一管理。如果自己创建,那么需要我们自己正确管理其创建和生命周期,因此不建议脱离TCommands来创建FUICommandInfo。

步骤二、绑定相关执行事件

绑定的时候用到FUICommandListMapAction函数来绑定命令和具体执行函数,FUICommandList中重载了多种MapAction的实现,主要就是几个常见的参数

  • TSharedPtr< const FUICommandInfo > InUICommandInfo:传入UICommandInfo对象
  • FExecuteAction:传入一个FExecuteAction对象,绑定了具体执行操作。
  • CanExecuteAction:传入一个FCanExecuteAction对象,用于检查当前操作是否能够执行,可以理解为一个条件检测函数,默认返回True
//.h 
TSharedPtr<class FUICommandList> PluginCommands;

//.cpp   
PluginCommands = MakeShareable(new FUICommandList);   //创建管理类
PluginCommands->MapAction( FTestMenuCommands::Get().PluginAction, TestFExecuteAction::CreateRaw(this, &FSlateTestModule::PluginButtonClicked), FCanExecuteAction());  // 创建绑定

步骤三、传入命令用于扩展编辑器

最后的步骤便是将命令与编辑器界面的某个UI进行结合,这里需要我们分别传入FUICommandInfoFUICommandList。还需要传入FUICommandList,原来就是为了获取FUICommandInfo对应的UIAction

UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
{
    FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
    {   
        // 传入命令FUICommandInfo
        FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FSlateTestCommands::Get().OpenPluginWindow));
        // 传入命令管理器FUICommandList(用于获取命令对应的Action)
        Entry.SetCommandList(PluginCommands);
    }
}

4.3 总结

UE编辑器利用命令FUICommandInfo解耦了UIAction和UI控件,使得整个界面的操作更加灵活和自定义。这种输入控制解耦的理念后面在UE5中被发扬光大到GamePlay中,也即UE5中的增强输入,增强输入系统更加复杂,且不局限于界面与命令的解耦,还包括与硬件之前的解耦输入。


四、Style.cpp文件介绍

4.1 认识Style

  • Style文件主要负责Slate的样式,Style样式在UE中常被用于UI的外貌打造,样式一般涉及按钮颜色、图片、字体、边框等具体的控件参数。

在插件模块中Style文件主要提供一些静态方法,来帮助插件模块创建、注册和注销FSlateStyleSet类型的单例

  • FSlateStyleSet是用于定义和管理SlateUI样式的类,我们可以在其中定义Slate插件方方面面的样式(按钮、文本框、滑块、复选框等),每种样式属性都可以以名称+属性值存储在FSlateStyleSet中,并在需要的时候调用。具体可以参考下面插件中的Style程序
TSharedRef< FSlateStyleSet > FSlateTestToolBarStyle::Create()
{
    /* 创建SlateStyleSet */
    TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("SlateTestStyle"));
    
    /* 设置样式集资产读取路径  */
    Style->SetContentRoot(IPluginManager::Get().FindPlugin("SlateTest")->GetBaseDir() / TEXT("Resources"));
    
    /* 存储名为 SlateTest.PluginAction 的BrushStyle到StyleSet中 */
    Style->Set("SlateTest.PluginAction", new IMAGE_BRUSH_SVG(TEXT("PlaceholderButtonIcon"), Icon20x20));
    return Style;
}

Slate插件中的Style程序中只定义了一个用于设置SlateTestToolBar.PluginAction命令对应的button的样式,这里用的一个划线的图片资产,编辑器中展示效果如图所示,这便是我们插件的工具栏入口按钮。



4.2 学以致用

问:我们可以脱离Style文件直接创建Style样式吗?
答:我们当然可以脱离插件的Style文件来使用FSlateStyleSet,但必须正确管理其生命周期,因此一般情况下还是不建议自己管理。
/* --------------注册并使用---------------*/
// 创建一个新的样式集
FSlateStyleSet* StyleSet = new FSlateStyleSet("MyStyleSet");

// 定义一个样式属性并存储到StyleSet
FSlateBrush ButtonBrush = FSlateBrush();
ButtonBrush.TintColor = FSlateColor(FLinearColor::Red);
StyleSet->Set("MyButtonStyle", ButtonBrush);

// 注册样式集
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);

// 在创建按钮时,使用这个样式
SNew(SButton)
    .ButtonStyle(StyleSet, "MyButtonStyle")
    .Text(FText::FromString("My Button"));


/* --------------注销---------------*/
// 使用完后,找合适的时机注销StyleSet
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
StyleSet.Reset();  


五、配置文件介绍

5.1 插件描述(.uplugin)文件介绍

上面目录中介绍到uplugin是插件的配置(描述)文件,主要用于配置插件的各种基本信息(版本号、作者等等),当然还用于来配置插件使用到的模块,该文件使用json的文件格式。该文件主要在游戏引擎启动期间去读取。

{
    "FileVersion": 3,
    "Version": 1,                    
    "VersionName": "1.0",
    "FriendlyName": "SlateTest",
    "Description": "", 
    "Category": "Other",            //作者
    "CreatedBy": "CloudBoy",
    "CreatedByURL": "",
    "DocsURL": "",
    "MarketplaceURL": "",
    "SupportURL": "",
    "CanContainContent": false,
    "IsBetaVersion": false,
    "IsExperimentalVersion": false,
    "Installed": false,
    "Modules": [
        {
            "Name": "SlateTest",                     //插件模块命名
            "Type": "Editor",                        //模块的类型,决定能加载模块的应用程序类型  EHostType::Type
            "LoadingPhase": "Default"                //模块加载策略  ELoadingPhase::Type
        }
    ]
}

这里我们主要来看下哪些参数会实质影响到项目的一些功能加载:

1)普通参数

  • CanContainContent
    是否包含资产目录,当为true的时候,UE会为我们默认创建一个Content目录


  • IsBetaVersion
    顾名思义该插件是否为测试版本,仅仅是一个提示作用,会在插件栏显示一个beta提示,并不影响我们对项目的打包。


  • IsExperimentalVersion
    是否为实验版本,打开这个小开关有什么后果呢?当然也只是一个提示,UE为了规范,拥有这个提示的插件是不建议被进行发布,望周知。


  • Installed
    这个参数官方的解释是Signifies that the plugin was installed on top of the engine,表示的是插件是否基于引擎安装,true则作为引擎的一部分安装,false则表示需要作为独立插件安装,而不是表示是否安装插件,当然这也只是一个标记。

2)Modules配置

Modules配置比较关键,有两个重要枚举参数设置,一个是模块类型Type,一个加载类型LodaingPhase,这两个配置会实实在在影响到插件的运行状态和加载策略,因此需要谨慎配置。

  • Type类型
先认识几个关键字:
【程序/Program】:UE可以创建独立的应用程序,不依赖编辑器启动,像UHT和PakViewer就是一个独立应用程序,在源码引擎Programs目录可以看到很多应用程序
【命令模式/Commandlet】:UE可以创建命令程序,和独立程序一样,不依赖编辑器即可独立启动,通常用于批处理UE中的资源,并且方便部署到流水线进行自动化批处理。常见的例子有:重定向器、打包Cook等,这些功能都是拉起一个命令程序来单独执行的。
namespace EHostType
{
    enum Type
    {
        Runtime,             //表示模块可以加载到所有目标中,除了【程序】
        RuntimeNoCommandlet, //表示模块可以加载到所有目标中,除了【程序】和运行【命令模式】的编辑器。
        RuntimeAndProgram,   //表示模块可以加载到所有目标中,包括支持的【程序】
        CookedOnly,          //表示模块只能在已经打包的游戏中加载
        UncookedOnly,        //表示模块只能在未打包的游戏中加载
        Developer,           //UE5中因存在歧义被废弃,不建议使用:表示模块只能在编辑器和【程序】中加载,可以在任何编辑器模式下加载(例如 -game、-server)
        Editor,              //表示模块只能在编辑器启动时加载
        EditorNoCommandlet,  //表示模块只能在编辑器启动时加载,但不能在【命令模式】下加载。
        EditorAndProgram,    //表示模块只能在编辑器和【程序】中加载
        Program,             //只有运行独立【程序】时的插件
        ServerOnly,          //表示模块可以加载到除了专用客户端之外的所有目标中
        ClientOnly,          //表示模块可以加载到除了专用服务器之外的所有目标中
        ClientOnlyNoCommandlet, //在客户端和编辑器下加载,但不能在 【命令模式】下
    };
}
  • LodaingPhase类型
namespace ELoadingPhase
{
    enum Type
    {
        PostConfigInit,         //引擎完全加载前,配置文件加载后。适用于较底层的模块。
        PreEarlyLoadingScreen,  //在UObject加载前,用于补丁系统
        PreLoadingScreen,       //在引擎模块完全加载和加载页面之前
        PreDefault,             //默认模块加载之前阶段
        Default,                //默认加载阶段,在引擎初始化时,游戏模块加载之后
        PostDefault,            //默认加载阶段之后加载
        PostEngineInit,         //引擎初始化后
        None,                   //不自动加载模块
    };
}

5.2 Build.cs文件介绍

当然,为代码添加注释可以帮助读者更好地理解Slate插件的创建过程。以下是完整代码及其注释:

// 在 #include 中添加您需要使用的模块
#include "Slate/Public/SlateBasics.h"
#include "Slate/Public/Widgets/Text/STextBlock.h"
#include "Slate/Public/Widgets/Input/SButton.h"
#include "Slate/Public/Widgets/Layout/SBox.h"

// 创建一个新的UE4插件项目
class FSlatePlugin : public IModuleInterface
{
public:
    /** IModuleInterface implementation */
    void StartupModule();
    void ShutdownModule();
};

void FSlatePlugin::StartupModule()
{
    // 在此处添加您的模块启动代码
}

void FSlatePlugin::ShutdownModule()
{
    // 在此处添加您的模块关闭代码
}

// 实现UMG插件
IMPLEMENT_MODULE(FSlatePlugin, SlatePlugin)

// 创建一个继承自 SCompoundWidget 的 Slate Widget
class SLATEPLUGIN_API SSlatePluginWidget : public SCompoundWidget
{
public:
    // 在 begin_args 和 end_args 中定义您的构造函数所需的任何成员变量
    SLATE_BEGIN_ARGS(SSlatePluginWidget) {}
    SLATE_END_ARGS()

    // 构造函数
    void Construct(const FArguments& InArgs)
    {
        // 定义子窗口
        ChildSlot
        [
            SNew(SBox)
            [
                SNew(STextBlock)
                .Text(FText::FromString("Hello, World!"))
            ]
        ];
    }
};

// 添加一个按钮到 Slate Widget
void Construct(const FArguments& InArgs)
{
    // 定义子窗口
    ChildSlot
    [
        SNew(SBox)
        [
            SNew(SButton)
            // 设置按钮文本
            .Text(FText::FromString("Click me!"))
            // 设置按钮点击事件
            .OnClicked(this, &SSlatePluginWidget::HandleButtonClick)
        ]
    ];
}

// 处理按钮点击事件
FReply HandleButtonClick()
{
    // 在此处添加您需要实现的代码
    return FReply::Handled();
}

// 将插件添加到项目中
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Slate", "SlateCore" });


六、创建Slate基础控件

6.1 认识链式编程

链式编程的形式如下面所示,通过SNew一个控件类型,然后通过.xxx来配置参数或者是绑定相关事件,然后再通过中括号包含其内容。



小提示:最外层的括号结尾记得加分号!

其实这个链式编程流程和UMG的层级插槽是一致的,可以参考下面这张图



6.2 创建Button实例

下面我们快速自己创建一个按钮来实践下这个流程,先把原有内容Sbox删除

在链式编程中创建控件实例其实我们有SNewSAssignNew两种方法,都可以通过其获得控件实例指针,但是SNew不强制赋值,SAssignNew必须提供类型指针



好了,我们开始创建Button,实现点击Button打印字符串,代码如下,关键代码在红色框选部分



在上图中,我们既配置了按钮上显示的字符,也配置了按钮点击时绑定的事件,我们编译后在UE4工具栏查看我们的插件,可以看到插件页面就有了一个大大的按钮。可以进行点击,然后会打印日志,说明我们的插件生效了



对于其他类型的基础控件,我们也可以举一反三,其实就和使用UMG时的方法是一样的,毕竟UMG就是基于Slate来进行创建的。

6.3 如何创建自定义控件

1 控件基类

UE4 Slate框架中最基础的类是SWidget,基于SWidget的子类主要由三种,分别是SCompoudWidgetSLeafWidgetSPanel\

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

  • SCompoudWidget
    其子类只能拥有一个子控件,常见的子类有SButton,SBorder等,他们的特点都是只能附加一个子控件
  • SLeafWidget
    其子类已经是叶子节点,不能再拥有子控件,常见的子类有SImageSTextBlock,这类控件都是没有子控件插槽
  • SPanel
    其子类的特点是可以无限添加子控件,没有数量限制,常见的子类的有SHorizontal(水平框)SPanel等等

详细介绍可见链接zhuanlan.zhihu.com/p/26

基于上面三种基类,UE4创建了许多基础样式为我们所用,我们也可以同样可以基于上述三种基类来创建我们自己的样式。

2 创建自定义控件

这里我们以创建一个继承于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来声明我们的需要的参数。这里详细可以参考链接:zhuanlan.zhihu.com/p/11中的内容。

这里我们以常用的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 isRight;来存放值,并在构造函数中为其赋值。

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)                   //初始化自定义参数
        ];
}

因为自定义控件中放的是一个按钮,因此结果插件显示的效果也是一个大的白色按钮


编辑于 2023-10-17 08:06・IP 属地上海