個人開發記錄筆記,隨緣更新
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