【UE4 C++】Slate 初探: Editor UI 與 Game UI


概述

名詞區分

  • 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

    image

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

從三類插件了解

  • 創建插件

    image

  • 點擊事件代碼對比

    image

控件展示案例,更改插件 MyEditorMode 代碼

  1. \Engine\Source\Runtime\AppFramework\Private\Framework\Testing 路徑下的文件,拷貝至 插件 Plugins\MyEditorMode\Source\MyEditorMode\Private
    • SUserWidgetTest.h
    • SUserWidgetTest.cpp
    • SWidgetGallery.h
    • SWidgetGallery.cpp
    • TestStyle.h
    • TestStyle.cpp
  2. vs 添加文件,或者右鍵工程 Generate Visual Stuido project files
  3. 編譯不通過
    • 頭文件問題,將頭文件改成當前目錄下的頭文件

    • 變量重名問題,注釋掉相應的變量聲明

    • 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()
    			]
    		];
    }
    

    image

創建 Runtime Slate

  • .build.cs 添加依賴模塊(如果自帶,可以取消注釋)

    PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
    

創建類

  • 圖示

    image

  • 創建 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();
    	}
    };
    

    image


查看工具

實際寫 Slate 的時候,可以多參考下源碼 Engine\Source\Runtime\Slate\Public\Widgets\

  • 顯示擴展點

    image

  • Widget Reflector

    image


參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM