概述

名词区分

  • 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. 【曹工杂谈】详解Maven插件调试方法

    前言 今年的更新频率简直是降至冰点了,一方面平时加班相对多一些了,下班只想玩手机:另一方面,好像进了大厂后,学习动力也很低了,总之就,很懒散,博客的话,今年都才只更新了不到5篇. 现在慢慢有一点状态, ...

  2. Hexo+Butterfly主题美化

    前言 本博客基于Hexo框架搭建,用到 hexo-theme-butterfly 主题(本人博客Butterfly版本3.4.0),hexo-theme-butterfly是基于Molunerfinn ...

  3. throw关键字

    1.基础用法 2.方法中加合法校验,告知方法的调用者 数组越界判断 3.一切皆为对象,创建的是运行期对象,则可以不处理(throws/try catch),直接交给JVM处理(打印并终止程序) 4.O ...

  4. c++ 游戏代码(1)

    迷宫代码如下: #include <iostream> #include <windows.h> #include <conio.h> using namespac ...

  5. Android View post 方法

    解析View.post方法.分析一下这个方法的流程. 说起post方法,我们很容易联想到Handler的post方法,都是接收一个Runnable对象.那么这两个方法有啥不同呢? Handler的po ...

  6. 彻底搞明白PHP中的include和require

    在PHP中,有两种包含外部文件的方式,分别是include和require.他们之间有什么不同呢? 如果文件不存在或发生了错误,require产生E_COMPILE_ERROR级别的错误,程序停止运行 ...

  7. 本地文件名大写,提交到git仓库后变成了小写

    输入改命令即可: git config core.ignorecase false

  8. php获取纳秒方法

    PHP不提供精度高于微秒的函数. 可以使用system功能,直接从机器中获得的价值,如果你运行的是Linux: $nanotime = system('date +%s%N');

  9. axios的简单的使用

    Axios 是什么? Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中. 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中).在服务端 ...

  10. js 命令模式 组合模式

    * 基本宏命令 var closeDoorCommand = { execute: function() { console.log("Closing the door..."); ...