【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


参考

posted @ 2021-05-29 23:38  砥才人  阅读(2914)  评论(4编辑  收藏  举报