首页 > 技术文章 > UE4笔记-UMG和Slate记录

linqing 2018-10-23 21:02 原文

个人开发记录笔记,随缘更新

UMG和Slate都属于UE4的UI系统的一部分: 

整套布局系统是很标准的C/S方式(Qt/WinForm)

UMG是基于原先的Slate封装开发的GUI.UE4提供了可视化编辑器用于用户编辑自己GUI系统同时UMG组件还添加了很多事件和方法并支持BP

Slate则是完全C++代码化的,所有的布局和组件创建只能用C++实现(Slate有一些更底层的组件,如SSplitter等,更便于开发复杂UI).

这篇随笔用于记录一些文档以外一些UMG和Slate的一些问题和混用例子(UPanelWidget和UContentWidget)

 

Umg文档:http://api.unrealengine.com/INT/Engine/UMG/index.html

Slate文档:http://api.unrealengine.com/INT/Programming/Slate/index.html

 其他一些文章Mark:

[UE4]Slate and Native UMG(C++) Notes: https://dawnarc.com/2018/12/ue4slate-and-native-umgc-notes/

Q.生命周期:

  UMG是居于UOBJECT的而Slate却是居于TSharedFromThis,所以UMG可以暴露于BP,而Slate只能应用于C++,而且声明周期也不尽相同:

    wait

  Umg:

 

 

  Slate:

     (懒癌附体,康心情补充)

Q.创建细节:

Umg:

 关于创建对象:

  因为UMG大多数都是BP类,所以当需要在C++创建时,需要通过TSubclassOf将BP类传回C++或使用LoadClass引用BP类:

  note:

    1.通常创建使用CreateWidget 函数,但是,如果想创建非UserWidget的类,如,UButton 等UContentWidget或UPanelWidget,可以用Construct Object from class函数来创建.免去无意义UUserWidget 封装

  C++创建BP类Widget的栗子:

UUserWidget* AMyProject2Character::CreateBPUserWidget(TSubclassOf<UUserWidget> SpecificBPClass)
{

    UUserWidget *newUserWidget = nullptr;
    UClass *SpecificBPClassFromCPlusPlus = LoadClass<UUserWidget>(NULL, TEXT("/Game/Blueprints/BPBaseWgt.BPBaseWgt_C"));
    if (SpecificBPClassFromCPlusPlus)
    {
      newUserWidget = CreateWidget<UUserWidget>(UGameplayStatics::GetPlayerController(GetWorld(), 0), SpecificBPClassFromCPlusPlus);
      check(newUserWidget)
    }

    return newUserWidget;

}

   

  关于UMG的C++与BP的混合使用:

  通常都会定义一个C++的UUserWidget类来作为BP UMG的基类,以暴露一些BP变量到C++中,

  一般不熟悉的情况下,会在BP中的Pre Construct 或Construct 事件下手动赋值到C++定义的变量上。

  事实上,可以选择使用UPROPERTY的Meta宏进行自动绑定

  如:绑定Editor编辑器定义的UMG的控件控件和动画类到C++基类的变量上

    UPROPERTY(BlueprintReadOnly, Category = "MainWidget", Meta = (BindWidget))
        UHorizontalBox *Container = nullptr;

    UPROPERTY(BlueprintReadOnly, Category = "MainWidget", Meta = (BindWidgetAnim))
        class UWidgetAnimation* Anim_Container = nullptr;

  当UMG继承了该基类,UE4会自动跟BP中名为Container 的容器和Anim_Container的动画 绑定

 

Slate的创建:

Slate在C++中 则是使用类似如下的方式创建:

TSharedPtr<SMySlateWidget> slateWidget = SNew(SMySlateWidget);

TSharedPtr<SMySlateWidget> MySlateWidget;
TSharedRef<SSplitter> MyWgtRef = SAssignNew( MySlateWidget, SMySlateWidget);

贴出SMySlateWidget实现:

.h

#pragma once
#include "CoreMinimal.h"
#include "SUserWidget.h"
class MYPROJECT2_API SMySlateWidget : public SUserWidget
{

public:
    SLATE_USER_ARGS(SMySlateWidget)
    {}
    SLATE_END_ARGS()

public:
    virtual void Construct(const FArguments& InArgs);
protected:
    FSlateBrush brush;
};

.cpp

#include "SMySlateWidget.h"
#include "Slate.h"
#include "SConstraintCanvas.h"

void SMySlateWidget::Construct(const FArguments& InArgs)
{
    TSharedRef<SBorder> border = SNew(SBorder);
    border->SetBorderBackgroundColor(FLinearColor::Red);
    border->SetForegroundColor(FLinearColor(0, 255, 0, 0.5));
    border->SetBorderImage(&brush);
    border->SetColorAndOpacity(FLinearColor::Green);

    SConstraintCanvas::FSlot &temp_slot = SConstraintCanvas::Slot();
    temp_slot.Anchors(FAnchors(0.0f, 0.0f, 1.0f, 1.0f))
        .Offset(FMargin(100.0f, 100.0f, 100.0f, 100.0f))
        .ZOrder(1)
        .AttachWidget(border);
    SUserWidget::Construct(
                            SUserWidget::FArguments()
                            [
                                SNew(SConstraintCanvas) + temp_slot
                            ] 
                          );
}

TSharedRef<SMySlateWidget> SMySlateWidget::New()
{
    return MakeShareable(new SMySlateWidget());
}

 

Q.在Slate中使用UMG组件:

  方法一:

    使用TakeWidget();函数转换成Slate即可

//temporary_wgt 是你的UUserWIdget类实例
    TSharedRef<SWidget> border = temporary_wgt->TakeWidget();

例如在RebuildWidget中:

TSharedRef<SWidget> UCppWgt_BaseSplitter::RebuildWidget()
{

  //temporary_wgt 是你的UUserWIdget类实例,自行Create Widget
  TSharedRef<SWidget> border = temporary_wgt->TakeWidget();

  SConstraintCanvas::FSlot &temp_slot = SConstraintCanvas::Slot();
    temp_slot.Anchors(FAnchors(0.0f, 0.0f, 1.0f, 1.0f))
        .Offset(FMargin(100.0f, 100.0f, 100.0f, 100.0f))
        .ZOrder(1)
        .AttachWidget(container);

    auto ret_wgt = SNew(SConstraintCanvas) + temp_slot;
    return  ret_wgt;
}

 

Q.混合使用:

方法一(覆盖形式):

如果想在UMG添加一个Slate的组件,那么你可以用UWidget子类简单封装一下,重载RebuildWidget,使用Slate的Widget来完全覆盖代替

这里就用上面创建的Slate:SMySlateWidget

例子:

.h

UCLASS()
class
项目_API UContenSlateWidget : public UUserWidget { GENERATED_BODY() public :
virtual const FText GetPaletteCategory() override; protected: virtual TSharedRef<SWidget> RebuildWidget() override; };

.cpp

const FText UContenSlateWidget::GetPaletteCategory()
{
    return NSLOCTEXT("UContenSlatetWidget","MyCustomSlate", "CustomSlate");
}

TSharedRef<SWidget> UContenSlateWidget::RebuildWidget() 
{
    TSharedRef<SMySlateWidget> mySlateCom = SNew(SMySlateWidget);
    
    return mySlateCom;
}

方法二:

重写RebuildWidget是混用最简单的方式,但是却无法在UMG编辑器里二次编辑扩展UMG类.
那么如果有相关需求,这个时候可以考虑TakeDerivedWidget函数来代替
重写RebuildWidget的方式

栗子:
待添加

Q.UPanelWidget和UContentWidget分析和栗子:

UPanelWidget和UContentWidget都是Slate对UMG暴露的封装基础实现类.

如UE4自带的UI组件:Border,Canvas,VerticalBox,SButton等都是基于以上两个类继承实现的

当你需要封装一些自定义组件的时候,可以继承它们或它们的子类

note:UContentWidget是UPanelWidget的子类,基于UPanelWidget重新封装实现的.

区别是:

  UPanelWidget是多个Slot的组件:例如VerticalBox

  UContentWidget是单个Slot的组件:例如Border,Button

源码分析:

  UPanelWidget:

    wait(懒癌附体,康心情补充)

  UContentWidget:

    wait(懒癌附体,康心情补充)

例子:

  基于UPanelWidget 自定义一个UMG 的Splitter的布局组件(CppWgt_SpliterComponent):

  需要扩展两个分别继承于UPanelSlot,UPanelWidget的类

    USplitterComponentSlot 和

    CppWgt_SpliterComponent

   Note:(这里只是对Spliter简单的UMG封装,需要自己根据情况扩展)

USplitterComponentSlot .h

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"
#include "Components/PanelSlot.h"
#include "Components/SlateWrapperTypes.h"

#include "Runtime/Slate/Public/Widgets/Layout/SSplitter.h"

#include "SplitterComponentSlot.generated.h"

UCLASS()
class 项目_API USplitterComponentSlot : public UPanelSlot
{
    GENERATED_UCLASS_BODY()
public :
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Layout|SSpliter Slot")
        float SizeValue = 1.0f;
public:

    void BuildSlot(TSharedRef<SSplitter> SplitterCom);

    // UPanelSlot interface
    virtual void SynchronizeProperties() override;
    // End of UPanelSlot interface

    virtual void ReleaseSlateResources(bool bReleaseChildren) override;

private:
    SSplitter::FSlot* Slot;
};

 

USplitterComponentSlot .cpp

  

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "SplitterComponentSlot.h"

#include "Components/Widget.h"

/////////////////////////////////////////////////////
// UHorizontalBoxSlot

USplitterComponentSlot::USplitterComponentSlot(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    Slot = NULL;
}

void USplitterComponentSlot::ReleaseSlateResources(bool bReleaseChildren)
{
    Super::ReleaseSlateResources(bReleaseChildren);
    Slot = NULL;
}

void USplitterComponentSlot::BuildSlot(TSharedRef<SSplitter> SplitterCom)
{
    Slot = &SplitterCom->AddSlot()
    [
        Content == NULL ? SNullWidget::NullWidget : Content->TakeWidget()
    ].Value(SizeValue);
}

void USplitterComponentSlot::SynchronizeProperties()
{
}

 

 

CppWgt_SpliterComponent.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"

#include "Runtime/UMG/Public/Components/PanelWidget.h"
#include "CppWgt_SpliterComponent.generated.h"

/**
 * 
 */
UCLASS()
class 项目_API UCppWgt_SpliterComponent : public UPanelWidget
{
    GENERATED_BODY()
public:

#if WITH_EDITOR
    // UWidget interface
    virtual const FText GetPaletteCategory() override;
    // End UWidget interface
#endif

    virtual void ReleaseSlateResources(bool bReleaseChildren) override;

protected:

    // UPanelWidget
    virtual UClass* GetSlotClass() const override;
    virtual void OnSlotAdded( UPanelSlot* Slot) override;
    virtual void OnSlotRemoved(UPanelSlot* Slot) override;
    // End UPanelWidget

protected:
    TSharedPtr<class SSplitter> MySplitter;

protected:
    // UWidget interface
    virtual TSharedRef<SWidget> RebuildWidget() override;
    // End of UWidget interface
};

 

CppWgt_SpliterComponent.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "CppWgt_SpliterComponent.h"

#include "Components/Border.h"
#include "Runtime/Slate/Public/Widgets/Layout/SBorder.h"

#include "Runtime/UMG/Public/Components/PanelSlot.h"
#include "SplitterComponentSlot.h"

#define LOCTEXT_NAMESPACE "UMG"

const FText UCppWgt_SpliterComponent::GetPaletteCategory()
{
    //UE_LOG(LogTemp, Log, TEXT(" GetPaletteCategory "));
    return LOCTEXT("", "QingUI");
}

void UCppWgt_SpliterComponent::ReleaseSlateResources(bool bReleaseChildren)
{
    Super::ReleaseSlateResources(bReleaseChildren);
    MySplitter.Reset();
}

UClass * UCppWgt_SpliterComponent::GetSlotClass() const
{
    UE_LOG(LogTemp, Log, TEXT(" GetSlotClass "));
    return USplitterComponentSlot::StaticClass();
}

void UCppWgt_SpliterComponent::OnSlotAdded(UPanelSlot * Slot)
{
    if (!MySplitter.IsValid())
    {
        return;
    }

    UE_LOG(LogTemp, Log, TEXT(" OnSlotAdded "));


    CastChecked< USplitterComponentSlot>(Slot)->BuildSlot(MySplitter.ToSharedRef());
}

void UCppWgt_SpliterComponent::OnSlotRemoved(UPanelSlot * Slot)
{
    //这里

  TSharedPtr<SWidget> Widget = Slot->Content->GetCachedWidget();
  if ( !MySplitter.IsValid() ||
    !Widget.IsValid() )
  {
    return;
  }

  FChildren* Children = MySplitter->GetChildren();


  for (int i = 0; i < Children->Num(); i++ )
  {
    TSharedRef<SWidget> tempWgt = Children->GetChildAt(i);


    if (Widget == tempWgt)
    {
      //Widget->SetVisibility(EVisibility::Hidden);
      MySplitter->RemoveAt(i);
      break;
    }
  }

 }


TSharedRef<SWidget> UCppWgt_SpliterComponent::RebuildWidget()
{
    MySplitter = SNew(SSplitter);

    
    for (UPanelSlot* PanelSlot : Slots) 
    {
        if (USplitterComponentSlot* TypedSlot = Cast<USplitterComponentSlot>(PanelSlot))
        {
            TypedSlot->Parent = this;
            TypedSlot->BuildSlot(MySplitter.ToSharedRef());

        }
    }
    
    return MySplitter.ToSharedRef();
}

#undef LOCTEXT_NAMESPACE

 

推荐阅读