【UE4 C++】Slate 初探: Editor UI 与 Game UI
阅读原文时间:2023年07月11日阅读:1

概述

  • Slate

    • Slate 是完全自定义、与平台无关的UI框架
    • 应用
      • 可用于编辑器UI,编辑器的大部分界面都是使用 Slate 构建的
      • 可做为游戏UI
      • 可作为独立应用开发
    • 只能 C++ 开发
    • 可以调用 UMG,使用TakeWidget()
  • HUD

    • HUD通常只显示,不互动
    • 可绘制文本、线条等
    • GameMode 设置
    • 可创建 UMG、Slate
  • UMG (Unreal Motion Graphics)

    • UMG是基于原先的Slate封装开发的GUI
    • 可在编辑设计,支持蓝图、C++访问
    • 支持访问 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()

从三类插件了解

  • 创建插件

  • 点击事件代码对比

控件展示案例,更改插件 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()
            ]
        ];
    }

  • .build.cs 添加依赖模块(如果自带,可以取消注释)

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

  • 创建 HUD派生类:AMyHUD

    #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&lt;class SMyCompoundWidget&gt; MyCompoundWidget;
    
    // 添加视口方法三
    TSharedPtr&lt;SWidget&gt; WidgetContainer;
    }; #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-&gt;GameViewport-&gt;AddViewportWidgetContent(MyCompoundWidget.ToSharedRef());
    
        // 添加视口方法二,此处无法移除,因为 weak widget
        //GEngine-&gt;GameViewport-&gt;AddViewportWidgetContent(
            //SNew(SWeakWidget).PossiblyNullContent(MyCompoundWidget.ToSharedRef()), 0);
    
        // 添加视口方法三,可被移除
        GEngine-&gt;GameViewport-&gt;AddViewportWidgetContent(
            SAssignNew(WidgetContainer,SWeakWidget).PossiblyNullContent(MyCompoundWidget.ToSharedRef()), 0);
    
        // 显示鼠标及设置输入模式
        APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
        if (PC)
        {
            PC-&gt;bShowMouseCursor = true;
            PC-&gt;SetInputMode(FInputModeUIOnly());
        }
    }
    } void AMyHUD::RemoveMySlate() { if (GEngine && GEngine->GameViewport && WidgetContainer.IsValid()) { // 移除添加视口方法一 GEngine->GameViewport->RemoveViewportWidgetContent(MyCompoundWidget.ToSharedRef());
        // 移除添加视口方法三
        GEngine-&gt;GameViewport-&gt;RemoveViewportWidgetContent(WidgetContainer.ToSharedRef());
    
        // 移除所有
        //GEngine-&gt;GameViewport-&gt;RemoveAllViewportWidgets();
    
        // 显示鼠标及设置输入模式
        APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
        if (PC)
        {
            PC-&gt;bShowMouseCursor = false;
            PC-&gt;SetInputMode(FInputModeGameOnly());
        }
    }
    }
  • 创建SCompoundWidget 派生类:SMyCompoundWidget

    #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&amp; InArgs);
    
    FReply OnPlayClicked() const;
    FReply OnQuitClicked() const;
    private: TWeakObjectPtr<AMyHUD> OwnerHUD; }; #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, &amp;SMyCompoundWidget::OnPlayClicked)
                    [
                        SNew(STextBlock)
                        .Font(ButtonTextStyle)
                        .Text(PlayText)
                        .Justification(ETextJustify::Center)
                    ]
                ]
    
                // Quit Button
                + SVerticalBox::Slot()
                .Padding(ButtonPadding)
                [
                    SNew(SButton)
                    .OnClicked(this, &amp;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

      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


参考

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器