Ilingis 发表于 2023-2-7 11:46

【UE5】使用Slate进行UI开发与编辑器拓展(三):自定义 ...

UE的UI系统——无论是为了gameplay常用的UMG,还是更常用的Slate,都提供了很多预设的控件来支持开发者。然而,有的时候,你可以能仍然需要自定义自己的控件。有的时候是为了更上一级的封装,有的时候是为了填充一些原生不含有的功能控件,或者为了性能着想。本篇我们将介绍如果自定义一个Slate控件,无论Gameplay还是Editor,它都是可用的
一. 创建自定义Slate控件

新建一个CustomWidget.h和CustomWidget.cpp,创建一个Slate模板最好的方法是使用Rider,Rider的预设模板含有很多Unreal中常用的模板,包括接下来我们会聊到的UE模块


注意前缀S,Slate控件以S为前缀。我们选择SCompoundWidget作为父类,它拥有一个插槽。创建后,我们首先来看类签名
class SLATELEARNING_API SCustomWidget : public SCompoundWidget
{
public:
        SLATE_BEGIN_ARGS(SCustomWidget)
        {
        }

        SLATE_END_ARGS()

        /** Constructs this widget with InArgs */
        void Construct(const FArguments& InArgs);
};
这里比较值得讨论的是SLATE_BEGIN_ARGS与SLATE_END_ARGS这两个宏,它用于实现我们的自定义Slate控件参数,以及实现参数默认值。
在Cpp文件中
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SCustomWidget::Construct(const FArguments& InArgs)
{
        /*
        ChildSlot
        [
                // Populate the widget
        ];
        */
}

END_SLATE_FUNCTION_BUILD_OPTIMIZATION
也有两个宏,BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION和END_SLATE_FUNCTION_BUILD_OPTIMIZATION。顾名思义,这两个宏是协助优化使用的,我们在这个Slate控件中的函数均会放到这两个宏之间。在文末我给出的参考资料中,作者认为这两个宏主要围绕Construct函数使用,但UE的官方代码中使用的更随意一些。
二.传递参数

在Slate控件中传递参数需要一些技巧,我们声明两个测试变量
class SLATELEARNING_API SCustomWidget : public SCompoundWidget
{
public:
        SLATE_BEGIN_ARGS(SCustomWidget)
        {
        }

        SLATE_ARGUMENT(float, FloatValue)
        SLATE_ARGUMENT(int32, IntValue)

        SLATE_END_ARGS()
       
        float FloatValue;
        int32 IntValue;

        /** Constructs this widget with InArgs */
        void Construct(const FArguments& InArgs);
};
有趣的是SLATE_ARGUMENT这个宏,宏签名为
SLATE_ARGUMENT( ArgType, ArgName )
这个宏的作用是,向FArguments中添加你声明的变量,在FArguments中,变量的名称为_FloatValue与_IntValue。
我们可以在SLATE_BEGIN_ARGS这个宏中用一些构造函数风格的方法来为这些变量赋于初值
class SLATELEARNING_API SCustomWidget : public SCompoundWidget
{
public:
        SLATE_BEGIN_ARGS(SCustomWidget) : _FloatValue(4.0f), _IntValue(2)
        {
        }

        SLATE_ARGUMENT(float, FloatValue)
        SLATE_ARGUMENT(int32, IntValue)

        SLATE_END_ARGS()
       
        float FloatValue;
        int32 IntValue;

        /** Constructs this widget with InArgs */
        void Construct(const FArguments& InArgs);
};
此时我们使用的_FloatValue与_IntValue实际上都是FArguments中的值,可以Construct函数中获取这些值:
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SCustomWidget::Construct(const FArguments& InArgs)
{
        float FValue = InArgs._FloatValue;
        int32 IValue = InArgs._IntValue;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
这样,我们就可以在构造Slate自定义控件时使用这些自定义值。
HorizontalBox.AddSlot()
[
        SNew(SCustomWidget).FloatValue(9.0f).IntValue(1)
];
这些就是我们在上一篇聊到的那些看起来很像链式调用一样的实质。实际上,在风格上,这更像是一种构造而非函数调用。
三. 绘制Slate自定义控件

绘制Slate自定义控件实际上是一个非常进阶的技巧,实现自定义的Slate绘制,通过这么一个函数:
virtual int32 OnPaint
(
    const FPaintArgs & Args,
    const FGeometry & AllottedGeometry,
    const FSlateRect & MyCullingRect,
    FSlateWindowElementList & OutDrawElements,
    int32 LayerId,
    const FWidgetStyle & InWidgetStyle,
    bool bParentEnabled
) const
我的评价是受不了。像这种再看一眼就会爆炸的函数签名在UE几乎无处不在。
首先,我们聊一聊他的返回值,它返回一个int32类型的值,这个值代表着这个控件以及他的子控件能够达到的最大层ID。这里的层ID是用于渲染的。
再看几个常用的重要参数

[*]AllottedGeometry 确定Widget的位置和尺寸
[*]OutDrawElements 接受用来绘制这个Widget所需要的绘制元素
[*]InWidgetStyle 确定颜色啊 透明度啊之类的样式信息
一旦你使用了自己的OnPaint函数实现,那就意味着你不想用组件的组合来创建你的控件,而是需要从无到有的创建你的组件绘制方法。
为了达到此目标,你需要用到一个工具类:FSlateDrawElement,它是我们渲染Slate控件的地方,它提供了一些最基础的 绘制线 绘制Box等工具APi。我们需要填充一个叫做FSlateVertex结构,用来表明顶点,,还有一个SlateIndex的数组,用来表示三角形的绘制。
写到这里,我发现我举得这个例子没什么好绘制的,因此,我把我学习的时候使用的参考资料贴在文章最后,里面包含了一个完整的OnPaint函数使用的案例。
我个人的经验是,如果使用现有组件的组合可以绘制的组件,就不需要使用OnPaint函数覆写的方法来实现,而Gameplay中使用,应该使用UI Shader来实现,其性能更好也更简单,OnPaint函数仅仅在Editor组件中可用,价值并不是特别大。
<hr/>参考资料:
页: [1]
查看完整版本: 【UE5】使用Slate进行UI开发与编辑器拓展(三):自定义 ...