UE4笔记-UMG和Slate记录
个人开发记录笔记,随缘更新
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