概述

名词区分

  • 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配置。--未完成

    配置K8s ##################################################################################### # 配置 --- ...

  2. appnium显式等待机制

    强制等待: sleep不推荐全局隐式等待 全局隐式等待: 在服务端等待 driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); ...

  3. Identity角色管理四(删除角色)

    角色删除方法 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Delete(string id) ...

  4. v-for列表渲染之数组变动检测

    1.简单举一个v-for列表渲染例子 <template> <div> <ul> <li v-for="item in items"> ...

  5. awk的执行方式

    https://blog.csdn.net/fengyuanye/article/details/82858863 awk执行有三种形式: 1.直接以命令行来执行,        语法形式为:awk  ...

  6. CentOS8安装ntp实现时间同步

    在CentOS8.0中默认不再支持ntp软件包,时间同步将由chrony来实现,像我这种习惯了ntp同步时间的,一时难以去适应chrony. 本文将通过wlnmp提供的源,来安装ntp服务 添加wln ...

  7. Shell系列(19)- 正则表达式

    正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配.grep,awk,sed等命令可以支持正则表达式. 通配符用来匹配符号条件的文件名,通配符是完全匹配.ls,find,c ...

  8. 『GoLang』包

    可见性规则 在Go语言中,标识符必须以一个大写字母开头,这样才可以被外部包的代码所使用,这被称为导出.标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的.但是包名不管 ...

  9. Winform 控件命名规范

    前言 最近 Winform 项目做得比较多,控件命名规范上常用的能记住,但是有些总要查,写个记录吧.方便以后自己用,大家也可以参考. 标准控件 序号 控件类型简写 控件类型 1 btn Button ...

  10. SpringBoot碰到的疑问或问题

    1.@ResponseBody 和 @RequestBody 的区别 @ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response ...