什么是 Slate?

Slate是一个完全自定义和平台无关的用户界面框架,旨在为工具和应用程序构建用户界面(User Interface),比如虚幻编辑器、游戏内UI,既有趣又高效。 它结合了声明性语法和轻松设计,布局和样式组件的功能,允许轻松创建和迭代UI。

 

Unreal Engine 心得
编辑器工具(自定义窗口)

 

Unreal Engine 心得
复杂的游戏用户界面

 

Slate 与 游戏UI的关系

我们的游戏中,在拼接用户界面时,并没有直接用到Slate,而是UWidget

Unreal Engine 心得
蓝图中的Border,是UBorder

 

 

 

Unreal Engine 心得
蓝图中的Button,是UButton

 

而这些UWidget的基本结构如下:

 

(伪代码)

UCLASS()

class UMG_API UWidget

{

    GENERATED_UCLASS_BODY()

public: // what we set to slate

    UPROPERTY(EditAnywhere)

    SlateStyle StyleLayout, StyleColor, StyleSize . . .

public: // what slate respond to us

    UPROPERTY(BlueprintAssignable)

    FOnEvent OnClickEvent, OnHoverEvent . . .

protected:  // slate

    TSharedPtr<Slate> MySlate;

}

 

本质上讲,UWidget是对Slate的一次封装,使其更方便、可视化地被开发者摆放位置,修改参数

Unreal Engine 心得

 

AddToViewPort

在执行UWidget的AddToViewPort方法时,其主要生效的代码为:

// 新建一个画布容器

TSharedRef<SConstraintCanvas> FullScreenCanvas = SNew(SConstraintCanvas);

FullScreenWidget = FullScreenCanvas;

 

// 获取当前UWidget所对应的SWidget

TSharedRef<SWidget> UserSlateWidget = TakeWidget();

// SWidget放入新建的画布中

FullScreenCanvas->AddSlot()

    .Offset(BIND_UOBJECT_ATTRIBUTE(FMargin, GetFullScreenOffset))

    .Anchors(BIND_UOBJECT_ATTRIBUTE(FAnchors, GetAnchorsInViewport))

    .Alignment(BIND_UOBJECT_ATTRIBUTE(FVector2D, GetAlignmentInViewport))

    [

        UserSlateWidget

    ];

……

// 将画布放入到统一的叠加层中

PinnedViewportOverlayWidget->AddSlot(ZOrder)

    [

        FullScreenCanvas

    ];

 

Unreal Engine 心得

 

Slate的种类

1. 包含动态数量子节点的 Panel

一般用来排版,比如横竖排版的SVerticalBox, SHorizontalBox,元素可以重叠的SOverlay等

2. 不包含子节点的 Leaf Widget

一个叶控件一般包含单一特定的功能,比如只绘制文字的STextBlock,只绘制图片的SImage

3. 包含显式命名、固定数量子节点的 Compound Widget

复合控件是实现一些明确功能的控件,系统自带的SButton, SBorder, SLogWidget, SScrollBox 我们游戏中的雷达图等。

 

 

Unreal Engine 心得
可以看到文字处理太重要了超出三界之外

 

Slate的特性

传递参数

对于一个slate类来说,往往在初始化的时候需要大量的参数。比如STextBlock,需要诸如文字本体、字体、高亮、换行等等20个参数,如果这些参数使用普通的 “Function(param1, param2…)” 形式来进行传递,那么整个程序会冗长又难读。因此,Unreal使用一系列复杂的宏使这个参数传递变的轻松而优雅。

 

声明

在SMySlate.h文件中使用SLATE_MACRO(ParamType, ParamName)进行声明,这些宏会定义一个FArguments的数据结构

class SLATE_API SMySlate : public SLeafWidget

{

public:

SLATE_BEGIN_ARGS(SMySlate)

: _Text()

, _TextStyle(&FCoreStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText"))

, _Font()

, _ColorAndOpacity()

, _HighlightColor()

, _AutoWrapText(false)

SLATE_ATTRIBUTE(FText, Text)

SLATE_STYLE_ARGUMENT(FTextBlockStyle, TextStyle)

SLATE_ATTRIBUTE(FSlateFontInfo, Font)

SLATE_ATTRIBUTE(FSlateColor, ColorAndOpacity)

SLATE_ATTRIBUTE(FLinearColor, HighlightColor)

SLATE_ATTRIBUTE(bool, AutoWrapText)

SLATE_EVENT( FOnClicked, OnDoubleClicked )

SLATE_END_ARGS()

}

传递


使用宏方法SNew进行逐个赋值,如果没有赋值,则使用默认值。SNew会调用目标类的Construct方法并将后续参数整合为FArguments的数据结构作为参数传入

void SParentSlate::InitView()

{

TSharedPtr<SMySlate> MySlate =

SNew(SMySlate)

.Text(FText::GetEmpty())

.TextStyle(FUAEItemGeneratorEditorStyle::Get(), "Text.NormalLog")

.ColorAndOpacity(FLinearColor::Gray)

.HighlightColor (FLinearColor::Red)

.AutoWrapText (true)

.OnDoubleClicked (this, &MyClass::OnMySlateDoubleClicked)

 

ChildSlot

[

MySlate.ToSharedRef()

];

}


接受

在SMySlate.cpp文件中,在Construct方法中接收传递的值

void MySlate::Construct( const FArguments& InArgs )

{

BoundText = InArgs._Text;

    TextStyle = *InArgs._TextStyle;

            ColorAndOpacity = InArgs._ColorAndOpacity;

            HighlightColor = InArgs._HighlightColor;

            AutoWrapText = InArgs._AutoWrapText;

    OnDoubleClicked = InArgs._OnDoubleClicked;

}


树状结构组合

当我们在面板中添加子控件的时候,slate重构了“[]”这个符号的功能,使我们能够在

c++程序中以树状结构来组织我们的代码

Unreal Engine 心得

不用一层一层地进行Append了,结合上述的独特参数传递方法,使得布局非常简洁明了。

 

式样

组件的式样信息(控件颜色、字号、字体、阴影、高光等),存在于FSlateStyleSet单例的TMap<FName, TSharedRef<struct FSlateWidgetStyle>>中。类似于HTMLCSS,控件的式样在独立的文件中被添加到上述单例中的式样表,在Slate中通过名称来使用。

 

声明与添加 FMySlateStyle.cpp

    FTextBlockStyle NormalTextBlockStyle = FTextBlockStyle(NormalText)

        .SetFont(TTF_FONT("Roboto-Bold", 12))

        .SetColorAndOpacity(FLinearColor(0.902f, 0.902f, 0.902f))

        .SetShadowOffset(FVector2D(2, 2))

        .SetShadowColorAndOpacity(FLinearColor(0.f, 0.f, 0.f, 0.7f));

    Style->Set("Text.NormalLog", NormalLogTextBlockStyle);

使用

MessagesTextBox = SNew(SMultiLineEditableTextBox)

        .TextStyle(FMySlateStyle::Get(), "Text.NormalLog")

 

Slate的重要属性&方法

DesiredSize

// 控件的占用空间

FVector2D DesiredSize;

 

// 计算出控件自身的占用空间

// LayoutScaleMultiplier = SlateUnit / ScreenPixel

// 对于没有子控件的SLeafWidget,只返回自己的占用空间

// 对于拥有子控件的SPanelSCompoundWidget,则遍历并驱动所有Visibility

// 不为Collapsed的子控件的这个方法,并根据其返回的值,来决定自己的大小范围。

// 执行顺序为自底至顶

FVector2D ComputeDesiredSize(float LayoutScaleMultiplier)

 

// 1.调用ComputeDesiredSize方法,2. 缓存其返回的值

Void CacheDesiredSize (float LayoutScaleMultiplier)

 

SlatePrepass

// 目前主要用来预计算组件的size,在控件Paint方法之前调用

// 而且主要是针对TextBlock类的文本组件

void SlatePrepass(float LayoutScaleMultiplier)

ArrangeChildren

// 获取所有的子控件

// 根据 “隐藏子控件的Tick是否被驱动来决定是否包含Collapse的子控件

// LeafWidget因为不包含子控件,所以此方法为空

ArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren)

 

Tick

// 控件是否驱动Tick方法

uint8 bCanTick

 

// 控件自身的Tick方法

// 我们在蓝图中注册的TickUWidgetTick,跟SlateTick木有关系

// 由Paint调用

void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )

 

ActiveTimer

// 活动的计时器列表

TArray<TSharedRef<FActiveTimerHandle>> ActiveTimers;

// 遍历计时器列表,并执行计时器

void ExecuteActiveTimers(double CurrentTime, float DeltaTime);

 

OnPaint

// 返回所有的自身与子控件的绘制元素列表

// FSlateWindowElementList& OutDrawElements

// 1. 通过ArrangeChildren获取所有子控件,电泳控件调用

// 2. 调用子控件的Paint方法

// 3. 对于LeafWidget,则生成FSlateDrawElement放入OutDrawElements

// SImageSTextBox FSlateDrawElement::MakeBox(OutDrawElements)

int32 OnPaint(Args… , FSlateWindowElementList& OutDrawElements, Args…)

 

获取返回的FSlateWindowElementList& OutDrawElements后,每个层级的SWidget会在外面画个Box.

驱动整个界面的SlateApplication获取的是所有窗口的所有元素,我们喜闻乐用的WidgetReflector就是拿这里面的值来进行映射,工程中自定义的WidgetOutliner也是拿此值进行映射。

 

SlateTick线程

Unreal Engine 心得

相关文章: