【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
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 代码
\Engine\Source\Runtime\AppFramework\Private\Framework\Testing
路径下的文件,拷贝至 插件Plugins\MyEditorMode\Source\MyEditorMode\Private
- SUserWidgetTest.h
- SUserWidgetTest.cpp
- SWidgetGallery.h
- SWidgetGallery.cpp
- TestStyle.h
- TestStyle.cpp
- vs 添加文件,或者右键工程 Generate Visual Stuido project files
- 编译不通过
-
头文件问题,将头文件改成当前目录下的头文件
-
变量重名问题,注释掉相应的变量声明
-
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
// 原文链接 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(); } };
查看工具
实际写 Slate 的时候,可以多参考下源码 Engine\Source\Runtime\Slate\Public\Widgets\
-
显示扩展点
-
Widget Reflector
参考
作者:砥才人
出处:https://www.cnblogs.com/shiroe
本系列文章为笔者整理原创,只发表在博客园上,欢迎分享本文链接,如需转载,请注明出处!