0%

UMG与Slate

11.7Slate细节

UE UMG Slate 详解

umg是umg,slate是slate,但是slate是umg的爹,umg拿着爹给的资源包装一下,就成了显示在HUD上的用户界面

比如UButton(umg),里头就有SButton(Slate)的引用,用的就是SButton的逻辑。 如果单看编辑器本身,那么编辑器用户界面基本上都是Slate,有一个理由是编辑器需要提供给用户,游戏引擎的用户界面有一部分是需要频繁改变的,比如UI编辑器,是用户自定义给玩家的UI界面,另一部分是大体固定的,比如一个类的细节面板,如果这个类属性标签上有蓝图可使用,那么这个属性在细节面板上就会多一个属性名,一个输入框,以及可能的一些其他周边按钮比如撤回按钮。 Slate的结构看起来是很扯淡的,比如:

	TSharedPtr<SVerticalBox> TempWidget;
    Widgets[3]->AddSlot()
    .AutoHeight()
    [
        SAssignNew(TempWidget, SVerticalBox)
        + SVerticalBox::Slot()
        .AutoHeight()
        .Padding(0.f, 0.f, 0.f, 0.f)
        [
            Showing[4]->CreatePropertyValueWidget()
        ]
        + SVerticalBox::Slot()
        .AutoHeight()
        [
            SNew(SExpandableArea)
            .AreaTitle(NSLOCTEXT("DetailCustomization", "Open", "Open"))
            .InitiallyCollapsed(true)
            .BodyContent()
            [
                VerticalBoxEx
            ]
        ]
    ];

给哥们干哪去了,这还是C++吗。 而且一个更重要的是,这些都是写死的代码,都是写死的组件引用,能不能轻易实现动态的组件还另说,光是更改界面需要重新编译就够吃一壶了。 总而言之就是Slate不属于UObject系统,他是基于web的方式来渲染UI的,SlateCore 负责UI逻辑和布局, SlateRHIRenderer负责UI的渲染。所以Slate当然不和蓝图系统坐一桌,但是UMG和蓝图绑定地死死的,毕竟UMG对Slate进行了包装,将SButtom包装成UButtom。

HUD 与 UMG 概念

细节面板、引擎工具栏、窗口栏由Slate实现,现在窗口里头有一张画布,画布上可以添加按钮,也可以添加其他UI,还能添加事件脚本,这个可以快速创建新UI的编辑器就是UMG。 HUD也是用户界面,但是他一般情况下特指不能互动的提示关键信息的UI,比如屏幕左上的血条,网络的ping值等,或者编辑器的性能面板也属于HUD,当然,UMG也可以将其制作的UI嵌入HUD。

UWidget与SWidget

UWidget是WidgetBlueprint里的可视化编辑控件,是向UObject暴露的,所以自然和蓝图系统有所联系. UWidget内有TakeWidget函数将SWidget转换成UWidget,函数内部逻辑就是将RebuildWidget调用了一次。 RebuildWidget被SWidget里所有的组件都重写了一遍,比如说UButton

TSharedRef<SWidget> UButton::RebuildWidget()
{
    MyButton = SNew(SButton)
        .OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
        .OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
        .OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
        .OnHovered_UObject( this, &ThisClass::SlateHandleHovered )
        .OnUnhovered_UObject( this, &ThisClass::SlateHandleUnhovered )
        .ButtonStyle(&WidgetStyle)
        .ClickMethod(ClickMethod)
        .TouchMethod(TouchMethod)
        .PressMethod(PressMethod)
        .IsFocusable(IsFocusable)
        ;

if ( GetChildrenCount() &gt; 0 )
{
    Cast&lt;UButtonSlot&gt;(GetContentSlot())-&gt;BuildSlot(MyButton.ToSharedRef());
}

return MyButton.ToSharedRef();

}

pAytpwD.md.png 这是SWidget相关继承图 SPanel中需要添加一系列的Slot(插槽)结构来进行UI布局编写,Overlay的意思是没有预定布局,所有后渲染的直接覆盖前渲染的,所以布局位置大小都需要自定义,BoxPanel指预定布局,比如说从垂直方向或者水平方向顺序放置Slot组件。 看一下FSlot的重载:

FSlot& operator[]( TSharedRef<SWidget> InWidget )
{
    SBoxPanel::FSlot::operator[](InWidget);
    return *this;
}

这下知道为什么Slate要用[]来写了,[]内返回SWidget组件,这样就可以用比如ChildSlot[SNew(...)]的方式为其添加子SWidget。 再看BoxPanel初始化宏

#define SLATE_SUPPORTS_SLOT( SlotType ) \
TArray< SlotType* > Slots; \
WidgetArgsType& operator + (SlotType& SlotToAdd) \
{ \
    Slots.Add( &SlotToAdd ); \
    return *this; \
}

也是重载了+当初Add函数。

然后就是最后一个和C++不太熟悉的点号

SLATE_BEGIN_ARGS( SButton )
    : _Content()
    , _ButtonStyle( &FCoreStyle::Get().GetWidgetStyle< FButtonStyle >( "Button" ) )
    , _TextStyle( &FCoreStyle::Get().GetWidgetStyle< FTextBlockStyle >("NormalText") )
    , _HAlign( HAlign_Fill )
    , _VAlign( VAlign_Fill )
    , _ContentPadding(FMargin(4.0, 2.0))
    , _Text()
    , _ClickMethod( EButtonClickMethod::DownAndUp )
    , _TouchMethod( EButtonTouchMethod::DownAndUp )
    , _PressMethod( EButtonPressMethod::DownAndUp )
    , _DesiredSizeScale( FVector2D(1,1) )
    , _ContentScale( FVector2D(1,1) )
    , _ButtonColorAndOpacity(FLinearColor::White)
    , _ForegroundColor( FCoreStyle::Get().GetSlateColor( "InvertedForeground" ) )
    , _IsFocusable( true )
    {
    }

/** Slot for this button&#39;s content (optional) */
SLATE_DEFAULT_SLOT( FArguments, Content )

/** The visual style of the button */
SLATE_STYLE_ARGUMENT( FButtonStyle, ButtonStyle )

使用了结构化构造,本身就是为属性赋值,比如_ButtonStyle属性,最终赋值到FArguments中,在使用SNew构造时进行传参。

Slate真没啥用,但是如果有要更改编辑器的需求,那么Slate是真的有用,就像前面说的,UMG管不到游戏单例局外,所以只能通过重写Slate来实现,相对于Unity来说真的是一个很老旧的实现方式。

生命周期

UMG基于UObject,而Slate基于TSharedFromThis。 UMG生命周期也就是UE的UI生命周期,基本上是这几个生命周期函数:

  • virtual void NativeOnInitialized();
  • virtual void NativePreConstruct();
  • virtual void NativeConstruct(); ---开始tick
  • virtual void NativeTick();
  • virtual void NativeDestruct(); ---结束tick

Slate,都说了是TSharedFromThis,那么生命周期就是由智能指针来管理的。

一些问答(防止被抓)

  • Slate中怎么用UMG TakeWidget,将UUserWidget转化为SWidget
  • 如何在UMG里混合使用Slate与UMG 也就是使用UWidget组件,而RebuildWidget是用来创造SWidget,所以思路是重写它,内容由UWidget来代替

//todo一些别的,比如UUserWidget还没概念