概述

名词区分

  • 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 代码

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

创建 Runtime Slate

  • .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<class SMyCompoundWidget> MyCompoundWidget; // 添加视口方法三
    TSharedPtr<SWidget> 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->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

    #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;
    };
    #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
    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


参考

【UE4 C++】Slate 初探: Editor UI 与 Game UI的更多相关文章

  1. UE4/Unity3d 根据元数据自动生成与更新UI

    大家可能发现一些大佬讲UE4,首先都会讲类型系统,知道UE4会根据宏标记生成一些特定的内容,UE4几乎所有高级功能都离不开这些内容,一般来说,我们不会直接去使用它. 今天这个Demo内容希望能加深大家 ...

  2. UE4之Slate: SImage

    概述 距离上次记录<UE4之Slate:纯C++工程配置>后已经好长时间了: 这个随笔来记录并分享一下SImage控件的使用,以在屏幕上显示一张图片: 目标 通过SImage控件的展示,学 ...

  3. iPhone/iPad/Android UI尺寸规范 UI尺寸规范,UI图标尺寸,UI界面尺寸,iPhone6尺寸,iPhone6 Plus尺寸,安卓尺寸,iOS尺寸

    iPhone/iPad/Android UI尺寸规范 UI尺寸规范,UI图标尺寸,UI界面尺寸,iPhone6尺寸,iPhone6 Plus尺寸,安卓尺寸,iOS尺寸 iPhone界面尺寸 设备 分辨 ...

  4. 学习通过Thread+Handler实现非UI线程更新UI组件

    [Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...

  5. HTML5 UI框架Kendo UI Web中如何创建自定义组件(二)

    在前面的文章<HTML5 UI框架Kendo UI Web自定义组件(一)>中,对在Kendo UI Web中如何创建自定义组件作出了一些基础讲解,下面将继续前面的内容. 使用一个数据源 ...

  6. iframeWin For Easy UI. 为 Easy UI 扩展的支持IFrame插件

    iframeWin For Easy UI. 为 Easy UI 扩展的支持IFrame插件 在一个项目中用了Easy UI,但是发现里面的 Dialog .Window.Messager 弹窗都不支 ...

  7. Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

    Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...

  8. 2D UI和3D UI的工作原理

    2D UI的工作原理 UI控件的位置在UI Root 的红框(视窗)上,也就是UI控件的z轴,相机的z轴,UI Root的z轴都是0,因为2D UI都是纯粹的2D图片按层次显示,不会不出现三维立体效果 ...

  9. 创新高性能移动 UI 框架-Canvas UI 框架

    WebView 里无法获得的能力虽然是「体验增强」与「端基本能力」,但现都基本上有成熟解决方法.但后期的 UI 和 Layout 的性能反而是目前 Web 技术欠缺的.所以,无论是 Titanium ...

随机推荐

  1. k8s 存活探针(健康检查)

    重启策略 (RestartPolicy ) Always:当容器终止退出后,总是重启容器,默认策略. OnFailure:当容器异常退出(退出状态码非0)时,才重启容器. Never:当容器终止退出, ...

  2. 20210819 Emotional Flutter,Medium Counting,Huge Counting,字符消除2

    考场 T1 一下想到了这题,将白块缩短 \(s\) 后维护类似的区间即可. T2 T3 俩计数,直接跳了. T4 的可行 \(t\) 集合相同相当与从 \(n\) 往前跳 kmp 数组,途径点相同,从 ...

  3. 最详尽的 JS 原型与原型链终极详解(1)(2)(3)===转载

    转载===方便以后复习 原文网址:https://www.jianshu.com/p/dee9f8b14771 一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为 ...

  4. 了解mysql concat()函数

    concat(arg1,arg2,....):将形参对应字段的值组合成一个字符串 假设:现在有一张学生表(test_user) 将这三个字段组合成一个字符串作为第四个字段 select test_us ...

  5. JS020. Array map()函数查到需要的元素时跳出遍历循环,不再执行到数组边界

    Array.prototype.map() map( )  方法创建一个 新数组 *,其结果是该数组中的每个元素是调用一次提供的 函数后的返回值 *.[ MDN / RUNOOB ] * map 添加 ...

  6. 面试官:MySQL的幻读是怎么被解决的?

    大家好,我是小林. 我之前写过一篇数据库事务的文章「 事务.事务隔离级别和MVCC」,这篇我说过什么是幻读. 在这里插入图片描述 然后前几天有位读者跟我说,我这个幻读例子不是已经被「可重复读」隔离级别 ...

  7. CodeForces - 764B Timofey and cubes(模拟)

    Young Timofey has a birthday today! He got kit of n cubes as a birthday present from his parents. Ev ...

  8. 浏览器缓存旧的js文件或css文件导致没出现预期效果

    最好在加载的js或css文件后加上 ?v=1.0.0 版本号,更新js后就更改一下版本号即可

  9. html阴影 box-shadow

    右下阴影 div { box-shadow: 10px 10px 5px #888888; }四周阴影 div { box-shadow: 0 0 5px #888888; } div {box-sh ...

  10. Linux系列(34) - yum源文件(1)

    yum源文件各参数含义 在[/etc/yum.repos.d/]目录中,默认有4个yum源文件,其中[CentOS-Linux-BaseOS.repo]是基本yum源文件,如果我们能上网,那它是默认生 ...