什么是 Slate?
Slate是一个完全自定义和平台无关的用户界面框架,旨在为工具和应用程序构建用户界面(User Interface),比如虚幻编辑器、游戏内UI,既有趣又高效。 它结合了声明性语法和轻松设计,布局和样式组件的功能,允许轻松创建和迭代UI。
Slate 与 游戏UI的关系
我们的游戏中,在拼接用户界面时,并没有直接用到Slate,而是UWidget
而这些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的一次封装,使其更方便、可视化地被开发者摆放位置,修改参数
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 ]; |
Slate的种类
1. 包含动态数量子节点的 Panel
一般用来排版,比如横竖排版的SVerticalBox, SHorizontalBox,元素可以重叠的SOverlay等
2. 不包含子节点的 Leaf Widget
一个叶控件一般包含单一特定的功能,比如只绘制文字的STextBlock,只绘制图片的SImage
3. 包含显式命名、固定数量子节点的 Compound Widget
复合控件是实现一些明确功能的控件,系统自带的SButton, SBorder, SLogWidget, SScrollBox 我们游戏中的雷达图等。
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++程序中以树状结构来组织我们的代码
不用一层一层地进行Append了,结合上述的独特参数传递方法,使得布局非常简洁明了。
式样
组件的式样信息(控件颜色、字号、字体、阴影、高光等),存在于FSlateStyleSet单例的TMap<FName, TSharedRef<struct FSlateWidgetStyle>>中。类似于HTML的CSS,控件的式样在独立的文件中被添加到上述单例中的式样表,在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,只返回自己的占用空间
// 对于拥有子控件的SPanel和SCompoundWidget,则遍历并驱动所有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方法
// 我们在蓝图中注册的Tick是UWidget的Tick,跟Slate的Tick木有关系
// 由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中
// 如SImage和STextBox的 FSlateDrawElement::MakeBox(OutDrawElements)
int32 OnPaint(Args… , FSlateWindowElementList& OutDrawElements, Args…)
获取返回的FSlateWindowElementList& OutDrawElements后,每个层级的SWidget会在外面画个Box.
驱动整个界面的SlateApplication获取的是所有窗口的所有元素,我们喜闻乐用的WidgetReflector就是拿这里面的值来进行映射,工程中自定义的WidgetOutliner也是拿此值进行映射。