概述
名詞區分
- Slate
- Slate 是完全自定義、與平台無關的UI框架
- 應用
- 可用於編輯器UI,編輯器的大部分界面都是使用 Slate 構建的
- 可做為游戲UI
- 可作為獨立應用開發
- 只能 C++ 開發
- 可以調用 UMG,使用TakeWidget()
- HUD
- HUD通常只顯示,不互動
- 可繪制文本、線條等
- GameMode 設置
- 可創建 UMG、Slate
- UMG (Unreal Motion Graphics)
- UMG是基於原先的Slate封裝開發的GUI
- 可在編輯設計,支持藍圖、C++訪問
- 支持訪問 Slate
Slate 框架
-
邏輯層部分 Slate、SlateCore
-
渲染部分 SlateRHIRenderer
-
基類為 SWidget
-
Slot 為槽,代表可以放置 子 Widget
Slate 的使用
-
聲明性語法——宏
SLATE_BEGIN_ARGS( SSubMenuButton ) : _ShouldAppearHovered( false ) {} /** 將顯示在按鈕上的標簽 */ SLATE_ATTRIBUTE( FString, Label ) /** 單擊按鈕時調用 */ SLATE_EVENT( FOnClicked, OnClicked ) /** 將放置在按鈕上的內容 */ SLATE_NAMED_SLOT( FArguments, FSimpleSlot, Content ) /** 在懸停狀態下是否應顯示按鈕 */ SLATE_ATTRIBUTE( bool, ShouldAppearHovered ) SLATE_END_ARGS()
-
SNew
- SNew( SlateWidget 類名 ),返回TSharedRef
- SNew(SWeakWidget).PossiblyNullContent()
-
SAssignNew
- SAssignNew( SlateWidget 智能指針,SlateWidget 類名),返回TSharedPtr.
- SAssignNew(SWidget, SWeakWidget).PossiblyNullContent()
創建 Editor Slate
從三類插件了解
-
創建插件
-
點擊事件代碼對比
控件展示案例,更改插件 MyEditorMode 代碼
\Engine\Source\Runtime\AppFramework\Private\Framework\Testing
路徑下的文件,拷貝至 插件Plugins\MyEditorMode\Source\MyEditorMode\Private
- SUserWidgetTest.h
- SUserWidgetTest.cpp
- SWidgetGallery.h
- SWidgetGallery.cpp
- TestStyle.h
- TestStyle.cpp
- vs 添加文件,或者右鍵工程 Generate Visual Stuido project files
- 編譯不通過
-
頭文件問題,將頭文件改成當前目錄下的頭文件
-
變量重名問題,注釋掉相應的變量聲明
-
LNK2019: 無法解析的外部符號 GetTestRenderTransform(void) 和 GetTestRenderTransformPivot(void),SWidgetGallery.cpp 中 MakeWidgetGallery 函數注釋掉相關語句,如下所示。
TSharedRef<SWidget> MakeWidgetGallery() { //extern TOptional<FSlateRenderTransform> GetTestRenderTransform(); //extern FVector2D GetTestRenderTransformPivot(); return SNew(SWidgetGallery); //.RenderTransform_Static(&GetTestRenderTransform) //.RenderTransformPivot_Static(&GetTestRenderTransformPivot); }
-
-
修改 MyEditorMode .cpp 種的 OnSpawnPluginTab 函數
TSharedRef<SDockTab> FMyEditorModeModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) { FTestStyle::ResetToDefault(); TSharedPtr<SWidget> ToolkitWidget; return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ // Put your tab content here! SAssignNew(ToolkitWidget, SBorder) [ MakeWidgetGallery() ] ]; }
創建 Runtime Slate
-
.build.cs 添加依賴模塊(如果自帶,可以取消注釋)
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
創建類
-
圖示
-
創建 HUD派生類:AMyHUD
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14826787.html #pragma once #include "CoreMinimal.h" #include "GameFramework/HUD.h" #include "MyHUD.generated.h" UCLASS() class DESIGNPATTERNS_API AMyHUD : public AHUD { GENERATED_BODY() public: virtual void BeginPlay() override; void ShowMySlate(); void RemoveMySlate(); // 沒有 include "MyCompoundWidget",而使用 class ,避免頭文件相互引用而編譯錯誤 TSharedPtr<class SMyCompoundWidget> MyCompoundWidget; // 添加視口方法三 TSharedPtr<SWidget> WidgetContainer; };
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14826787.html #pragma once #include "MyHUD.h" #include "Kismet/GameplayStatics.h" #include "SMyCompoundWidget.h" #include "Widgets/SWeakWidget.h" void AMyHUD::BeginPlay() { Super::BeginPlay(); ShowMySlate(); } void AMyHUD::ShowMySlate() { if (GEngine && GEngine->GameViewport) { // 第二個參數為 ZOrder,默認為 0 //GEngine->GameViewport->AddViewportWidgetContent(SNew(SMyCompoundWidget), 0); //GEngine->GameViewport->AddViewportWidgetContent(SAssignNew(MyCompoundWidget, SMyCompoundWidget)); // MyCompoundWidget = SNew(SMyCompoundWidget).OwnerHUDArg(this); //SAssignNew(MyCompoundWidget, SMyCompoundWidget); // 添加視口方法一,可被移除 //GEngine->GameViewport->AddViewportWidgetContent(MyCompoundWidget.ToSharedRef()); // 添加視口方法二,此處無法移除,因為 weak widget //GEngine->GameViewport->AddViewportWidgetContent( //SNew(SWeakWidget).PossiblyNullContent(MyCompoundWidget.ToSharedRef()), 0); // 添加視口方法三,可被移除 GEngine->GameViewport->AddViewportWidgetContent( SAssignNew(WidgetContainer,SWeakWidget).PossiblyNullContent(MyCompoundWidget.ToSharedRef()), 0); // 顯示鼠標及設置輸入模式 APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0); if (PC) { PC->bShowMouseCursor = true; PC->SetInputMode(FInputModeUIOnly()); } } } void AMyHUD::RemoveMySlate() { if (GEngine && GEngine->GameViewport && WidgetContainer.IsValid()) { // 移除添加視口方法一 GEngine->GameViewport->RemoveViewportWidgetContent(MyCompoundWidget.ToSharedRef()); // 移除添加視口方法三 GEngine->GameViewport->RemoveViewportWidgetContent(WidgetContainer.ToSharedRef()); // 移除所有 //GEngine->GameViewport->RemoveAllViewportWidgets(); // 顯示鼠標及設置輸入模式 APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0); if (PC) { PC->bShowMouseCursor = false; PC->SetInputMode(FInputModeGameOnly()); } } }
-
創建SCompoundWidget 派生類:SMyCompoundWidget
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14826787.html #include "CoreMinimal.h" #include "Widgets/SCompoundWidget.h" #include "MyHUD.h" /** * */ class DESIGNPATTERNS_API SMyCompoundWidget : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SMyCompoundWidget) {} // 添加參數 SLATE_ARGUMENT(TWeakObjectPtr<AMyHUD>, OwnerHUDArg); SLATE_END_ARGS() /** Constructs this widget with InArgs */ void Construct(const FArguments& InArgs); FReply OnPlayClicked() const; FReply OnQuitClicked() const; private: TWeakObjectPtr<AMyHUD> OwnerHUD; };
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14826787.html #include "SMyCompoundWidget.h" #include "SlateOptMacros.h" #include "Widgets/Images/SImage.h" #include "MyHUD.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet/GameplayStatics.h" #include "Widgets/Layout/SBackgroundBlur.h" #define LOCTEXT_NAMESPACE "MyNamespace" BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SMyCompoundWidget::Construct(const FArguments& InArgs) { // 注意此處帶下划線 OwnerHUD = InArgs._OwnerHUDArg; // 文本和按鈕間距設置 const FMargin ContentPadding = FMargin(500.0f, 300.0f); const FMargin ButtonPadding = FMargin(10.f); // 按鈕和標題文本 const FText TitleText = LOCTEXT("SlateTest", "Just a Slate Test"); const FText PlayText = LOCTEXT("PlayGame", "Play"); const FText QuitText = LOCTEXT("QuitGame", "Quit Game"); //按鈕字體及大小設置 FSlateFontInfo ButtonTextStyle = FCoreStyle::Get().GetFontStyle("EmbossedText"); ButtonTextStyle.Size = 40.f; //標題字體及大小設置 FSlateFontInfo TitleTextStyle = ButtonTextStyle; TitleTextStyle.Size = 60.f; //所有UI控件都寫在這里 ChildSlot [ SNew(SOverlay) + SOverlay::Slot() .HAlign(HAlign_Fill).VAlign(VAlign_Fill) [ SNew(SImage) // 背景(半透明黑) .ColorAndOpacity(FColor(0,0,0,127)) ] + SOverlay::Slot() .HAlign(HAlign_Fill).VAlign(VAlign_Fill) [ SNew(SBackgroundBlur) // 高斯模糊 .BlurStrength(10.0f) ] + SOverlay::Slot() .HAlign(HAlign_Fill).VAlign(VAlign_Fill) .Padding(ContentPadding) [ SNew(SVerticalBox) // Title Text + SVerticalBox::Slot() [ SNew(STextBlock) .Font(TitleTextStyle) .Text(TitleText) .Justification(ETextJustify::Center) ] // Play Button + SVerticalBox::Slot() .Padding(ButtonPadding) [ SNew(SButton) .OnClicked(this, &SMyCompoundWidget::OnPlayClicked) [ SNew(STextBlock) .Font(ButtonTextStyle) .Text(PlayText) .Justification(ETextJustify::Center) ] ] // Quit Button + SVerticalBox::Slot() .Padding(ButtonPadding) [ SNew(SButton) .OnClicked(this, &SMyCompoundWidget::OnQuitClicked) [ SNew(STextBlock) .Font(ButtonTextStyle) .Text(QuitText) .Justification(ETextJustify::Center) ] ] ] ]; } FReply SMyCompoundWidget::OnPlayClicked() const { if (OwnerHUD.IsValid()) { OwnerHUD->RemoveMySlate(); } return FReply::Handled(); } FReply SMyCompoundWidget::OnQuitClicked() const { if (OwnerHUD.IsValid()) { OwnerHUD->PlayerOwner->ConsoleCommand("quit"); } return FReply::Handled(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION #undef LOCTEXT_NAMESPACE
-
創建 GameModeBase派生類:AMyPlayerController ,PlayerController派生類:AMyPlayerController
- 設定 PlayerControllerClass 為 AMyPlayerController
- 設定 HUDClass 為AMyHUD
- 關卡 World Setting->GameMode Override 設置為 MyGameMode
// 原文鏈接 https://www.cnblogs.com/shiroe/p/14826787.html UCLASS() class DESIGNPATTERNS_API AMyPlayerController : public APlayerController { GENERATED_BODY() }; UCLASS() class DESIGNPATTERNS_API AMyGameMode : public AGameModeBase { GENERATED_BODY() public: AMyGameMode() { PlayerControllerClass = AMyPlayerController::StaticClass(); HUDClass = AMyHUD::StaticClass(); } };
查看工具
實際寫 Slate 的時候,可以多參考下源碼 Engine\Source\Runtime\Slate\Public\Widgets\
-
顯示擴展點
-
Widget Reflector