当前位置:   article > 正文

UE4开发C++沙盒游戏教程笔记(一)(对应教程集数 2 ~ 5)_ue的c++教程

ue的c++教程

致谢

感谢用户 ysjooouss 指出 “使用 WidgetStyle 设置样式” 一章 SlAiCourse.h 头文件的内容没有贴出来,导致后续 SlAiStyle::Get() 返回 NULL 的问题。

前言

本文用于对梁迪老师的课程《UE4纯C++与Slate开发沙盒游戏》进行个人的知识点梳理和学习总结,此课程可在腾讯课堂上找到。笔者使用的引擎版本是 4.26.2。

此课程适合对虚幻4引擎的 Gameplay 和 C++ 语法(也常被称为U++)有一定了解的人学习,如果刚刚从蓝图转过来可能会看得一头雾水,建议先去补充一下这些前置知识。

本文不允许转载,因为个人不喜欢看了一整篇文章后,发现作者不是这个博主,然后为了追根溯源要一直点链接跳转。如果你觉得有用可以收藏或者分享本文。

此外,本文并尽量只贴一次对应集数里面的最终代码,所以会按照自己的理解去安排贴出来的顺序。此外为了简洁,部分位置会略过一些代码(比如在前面的课程已经写过的代码)。建议本文的食用方式是用于复习回顾,或者参考代码以解决一些找了很久都没有头绪的问题。如果只是想着复制粘贴代码,以此省去跟着教程写代码、解决问题的过程,可能你会发现看了课程下来也是管中窥豹,所得甚少。私以为学习之事不可囫囵吞枣,在当前这个浮躁的社会氛围中,能够沉下心来去钻研是难能可贵的。

1. 添加 Slate 到界面

创建 Gamemode 、 PlayerController 和 HUD

添加一个C++类的 GameMode ,取名叫 SlAiMenuGameMode,路径为 /Public/GamePlay/,作用是作为菜单页面的游戏模式。

再添加一个 C++ 类的 PlayerController,取名叫 SlAiMenuController,路径为 /Public/GamePlay/,作用是作为菜单页面的玩家控制器。

再添加一个 C++ 类的 HUD,取名叫 SlAiMenuHUD,路径为 /Public/UI/HUD,作用是管理菜单页面的众多 Widget 界面。

C++文件都创建好了,接下来就是把模块依赖添加进项目的 .Build.cs 文件。

SlAiCourse.Build.cs

using UnrealBuildTool;

public class SlAiCourse : ModuleRules
{
	public SlAiCourse(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { 
		"Core", "CoreUObject", 
		"Engine", "InputCore", 
		"Slate", "SlateCore"});	// Slate 开发依赖到这两个模块

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

关于模块依赖的知识点,推荐看这两篇文章:

《UE4中Target.cs、Build.cs的区别》 (比较简洁)
《大象无形UE4笔记五:模块总结》 (非常详细)

接下来完善前面创建的 C++ 文件。

关于 GENERATED_BODY()GENERATED_UCLASS_BODY() 这两个宏的区别:

用前者,构造函数需要在 .h 头文件声明,然后 .cpp 文件内实现;(4.17版本后一般用这个)

用后者,可以不在 .h 头文件声明构造函数,但是在 .cpp 文件里实现的时候要加参数 (const FObjectInitializer& ObjectInitializer)

SlAiMenuGameMode.h

UCLASS()
class SLAICOURSE_API ASlAiMenuGameMode : public AGameModeBase
{
	GENERATED_BODY()

public:

	ASlAiMenuGameMode();	
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

SlAiMenuGameMode.cpp

#include "GamePlay/SlAiMenuGameMode.h"
#include "SlAiMenuHUD.h"
#include "SlAiMenuController.h"

ASlAiMenuGameMode::ASlAiMenuGameMode()
{
	// StaticClass() 是 UObject 的一个成员类,通过反射获取 UClass
	PlayerControllerClass = ASlAiMenuController::StaticClass(); 
	HUDClass = ASlAiMenuHUD::StaticClass();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

SlAiMenuController.h

UCLASS()
class SLAICOURSE_API ASlAiMenuController : public APlayerController
{
	GENERATED_BODY()

public:

	ASlAiMenuController();
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

SlAiMenuController.cpp

#include "GamePlay/SlAiMenuController.h"

ASlAiMenuController::ASlAiMenuController()
{
	bShowMouseCursor = true;	// 显示鼠标
}

// 不在构造函数里设置输入模式是因为在 BeginPlay() 的生命周期里设置更适合
void ASlAiMenuController::BeginPlay()
{
	FInputModeUIOnly InputMode;		// 定义一个仅对UI生效的输入模式
	//将输入模式的鼠标锁定枚举设置为总是锁定在屏幕内
	InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);	
	SetInputMode(InputMode);		// 使作用于 PlayerController
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

创建两个菜单 Widget

添加两个C++类的 SlateWidget ,分别取名叫 SlAiMenuHUDWidgetSlAiMenuWidget,路径皆为 /Public/UI/Widget/。前者作为菜单主界面,后者作为菜单栏界面。(注:生成的 SlateWidget 文件会自动在名字前面加一个字母 “S”)

随后在 HUD 里面添加菜单主界面到游戏视口。

SlAiMenuHUD.h

UCLASS()
class SLAICOURSE_API ASlAiMenuHUD : public AHUD
{
	GENERATED_BODY()

public:

	ASlAiMenuHUD();
	// 主菜单页面的指针
	TSharedPtr<class SSlAiMenuHUDWidget> MenuHUDWidget;	
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

SlAiMenuHUD.cpp

#include "UI/HUD/SlAiMenuHUD.h"
#include "SSlAiMenuHUDWidget.h"
#include "SlateBasics.h"

ASlAiMenuHUD::ASlAiMenuHUD()
{
	if (GEngine && GEngine->GameViewport) {
		SAssignNew(MenuHUDWidget, SSlAiMenuHUDWidget);
		GEngine->GameViewport->AddViewportWidgetContent(
		SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef()));
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

关于向视口中添加控件,推荐这篇文章:

《UE4在游戏中使用Slate》

关于 SNew() 和 SAssignNew() 的区别,推荐这篇文章:

《Slate中的SNew与SAssignNew区别》

2. 使用 WidgetStyle 设置样式

创建样式类 SlateWidgetStyle 和 注册样式的普通 C++ 类

创建 C++ 样式类 SlateWidgetStyle, 取名叫 SlAiMenuStyle,路径为 /Public/UI/Style,用于统一管理整个菜单界面内 Slate 控件的样式。(注:生成这个类的时候其文件名会固定后缀为 [xxx]WidgetStyle,但代码里的类的命名依旧是生成界面里取的名字。后面为了统一,会采用 SlAiMenuStyle 这个名字。)

再创建一个普通的 C++ 类,取名叫 SlAiStyle,路径为 /Public/UI/Style,其作用是获取上面的样式类,并且把样式类注册进游戏。

SlAiStyle.h

class SLAICOURSE_API SlAiStyle
{
public:
	// 下面都是单例模式需要用到的方法
	static void Initialize();

	static FName GetStyleSetName();

	static void ShutDown();

	static const ISlateStyle& Get();

private:

	static TSharedRef<class FSlateStyleSet> Create();

	static TSharedPtr<FSlateStyleSet> SlAiStyleInstance;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

SlAiStyle.cpp

#include "UI/Style/SlAiStyle.h"
#include "SlateGameResources.h"		// 引入 Create() 方法中用到的资源获取类头文件

// 该类使用单例模式
TSharedPtr<FSlateStyleSet> SlAiStyle::SlAiStyleInstance = NULL;

void SlAiStyle::Initialize()
{
	if (!SlAiStyleInstance.IsValid()) {
		SlAiStyleInstance = Create();
		FSlateStyleRegistry::RegisterSlateStyle(*SlAiStyleInstance);	// 注册这个 Sytle
	}
}

FName SlAiStyle::GetStyleSetName()
{
	static FName StyleSetName(TEXT("SlAiStyle"));
	return StyleSetName;
}

void SlAiStyle::ShutDown()
{
	FSlateStyleRegistry::UnRegisterSlateStyle(*SlAiStyleInstance);
	ensure(SlAiStyleInstance.IsUnique());
	SlAiStyleInstance.Reset();
}

const ISlateStyle& SlAiStyle::Get()
{
	return *SlAiStyleInstance;
}

TSharedRef<class FSlateStyleSet> SlAiStyle::Create()
{
	TSharedRef<FSlateStyleSet> StyleRef = 
	FSlateGameResources::New(SlAiStyle::GetStyleSetName(), 
	"/Game/UI/Style", "/Game/UI/Style");	// 提供 Style 的地址
	// ↓ 课程最后添加的,直接在样式类的引用中调用方法添加字体样式
	// 在第 5 节课《菜单布局实现》会用到
	// 笔者用的 4.26.2 版本按照老师的代码无法正常获取字体样式,所以经过尝试后做了些许改动
	StyleRef->Set("MenuItemFont", FSlateFontInfo(TEXT("Slate/Fonts/Roboto-Regular.ttf"), 50));
	return StyleRef;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

关于这个类的另外两种使用情景,推荐下面文章:
《UE Slate样式》
《unreal 自定义 Slate Style Sets》

对于 FName、FString、FText 的区别,推荐下面两篇文章:
《UE4随笔:FString、FName 与 FText》 (比较详细)
《UE4中 FName FText FString的区别》 (简洁易懂)

在项目模组加载和关闭的时候需要调用样式获取类的初始化和销毁方法。

SlAiCourse.h

class FSlAiCourseModule : public FDefaultGameModuleImpl		// 继承这个模组类
{
public:
	// 重写这两个方法,它们分别会在项目刚开始和结束自动被执行
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

SlAiCourse.cpp

#include "SlAiCourse.h"
#include "Modules/ModuleManager.h"
#include "SlAiStyle.h"	// 引入头文件


void FSlAiCourseModule::StartupModule()
{
	// 初始化样式
	FSlateStyleRegistry::UnRegisterSlateStyle(SlAiStyle::GetStyleSetName());
	SlAiStyle::Initialize();
}

void FSlAiCourseModule::ShutdownModule()
{
	SlAiStyle::ShutDown();
}

// 该宏的第一个参数需要指定为本项目的模组名
IMPLEMENT_PRIMARY_GAME_MODULE( FSlAiCourseModule, SlAiCourse, "SlAiCourse" );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

随后在刚刚创建的 SlAiMenuStyle 里创建一个笔刷。

SlAiMenuStyle.h

#pragma once

#include "CoreMinimal.h"
#include "Styling/SlateWidgetStyle.h"
#include "Styling/SlateWidgetStyleContainerBase.h"
#include "SlateBrush.h"	//引入笔刷头文件,下面这个 generated.h 结尾的头文件必须在最后一个引入

#include "SlAiMenuWidgetStyle.generated.h"

USTRUCT()
struct SLAICOURSE_API FSlAiMenuStyle : public FSlateWidgetStyle
{
	GENERATED_USTRUCT_BODY()

	FSlAiMenuStyle();
	virtual ~FSlAiMenuStyle();

	// FSlateWidgetStyle
	virtual void GetResources(TArray<const FSlateBrush*>& OutBrushes) const override;
	static const FName TypeName;
	virtual const FName GetTypeName() const override { return TypeName; };
	static const FSlAiMenuStyle& GetDefault();

	// 此处添加一个笔刷,用过 UMG 的应该都知道笔刷相当于一个图片引用位
	UPROPERTY(EditAnywhere, Category = "MenuHUD")
	FSlateBrush MenuHUDBackgroundBrush;
}
//... 后面省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

测试样式类的运用

要运用样式类中的样式到 Widget,则需要先获取这个样式类。

SSlAiMenuHUDWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"

class SLAICOURSE_API SSlAiMenuHUDWidget : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlAiMenuHUDWidget)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:
	// 获取 Menu 样式
	const struct FSlAiMenuStyle* MenuStyle;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

随后在 .cpp 中继续补充获取样式类的代码;并将之前上一集创建的一个示例用 SButton 替换为 SImage,用于测试样式类的效果。

SSlAiMenuHUDWidget.cpp

#include "UI/Widget/SSlAiMenuHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"		// 引入这个 SImage 专用头文件
#include "SlAiStyle.h"	// 引入样式获取类
#include "SlAiMenuWidgetStyle.h"	// 引入样式类

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuHUDWidget::Construct(const FArguments& InArgs)
{
	// 获取编辑器的 MenuStyle,
	// 随后会在编辑器中创建这个样式类的一个蓝图实例,取名 BPSlAiMenuStyle
	MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");

	ChildSlot
	[
		SNew(SImage)	// 换成这个
		.Image(&MenuStyle->MenuHUDBackgroundBrush)	// 指定该 Image 控件使用样式类中定义的笔刷
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

关于 generated.h 相关的知识点,推荐下面的文章:
[UE4官方直播学习记录]虚幻C++进阶之路|大钊(二)
建议从(一)开始阅读,可以对 UE4 的反射有初步了解。

随后,在主目录下新建一个叫 UI 的文件夹,再在该文件夹内新建 Style 文件夹(用来存放样式类的实例),在这个文件夹里新建一个 Slate Widget Style,以 SlAiMenuWidgetStyle 为基类,取名叫 BPSlAiMenuStyle。在里面把 Menu HUDBackgoundBrush 的 Image 引用设置为 BGImage。

运行游戏,即可看到样式应用成功。

3. DPI 屏幕适配

编写一个自定义的缩放规则

由于默认的缩放规则不理想,所以本节课演示如何编写自己需要的屏幕适配缩放规则。

首先在样式类给菜单栏背景添加一个笔刷。

SlAiMenuStyle.h

// 没有变更或添加的代码段就不贴出来了,有变更的地方我会给注释

struct SLAICOURSE_API FSlAiMenuStyle : public FSlateWidgetStyle
{


	UPROPERTY(EditAnywhere, Category = "MenuHUD")
	FSlateBrush MenuHUDBackgroundBrush;

	// 指定 Menu 的背景图片
	UPROPERTY(EditAnywhere, Category = "Menu")
	FSlateBrush MenuBackgroundBrush;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后在菜单主页面的头文件写好自定义缩放规则用到的变量和函数。

SSlAiMenuHUDWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"

class SLAICOURSE_API SSlAiMenuHUDWidget : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlAiMenuHUDWidget)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:
	// 绑定 UIScaler 的函数
	float GetUIScaler() const;
	// 获取屏幕尺寸
	FVector2D GetViewportSize();

private:
	const struct FSlAiMenuStyle* MenuStyle;
	// DPI 缩放系数
	TAttribute<float> UIScaler;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

然后更改一下菜单主页面的结构,补全缩放规则的变量赋值和函数实现。

SlAiMenuHUDWidget.cpp

#include "UI/Widget/SSlAiMenuHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"	
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "SOverlay.h"	// 引入 SOverlay 专用头文件
#include "Engine/Engine.h"	// 引入用于判断界面尺寸 
#include "SDPIScaler.h"	// 引入可以根据比例数值缩放内容的容器

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuHUDWidget::Construct(const FArguments& InArgs)
{
	MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
	// 绑定缩放规则方法,相当于这个变量会以一定频率被这个绑定的方法所赋值
	UIScaler.Bind(this, &SSlAiMenuHUDWidget::GetUIScaler);

	ChildSlot
	[
		SNew(SDPIScaler)	// 添加这个缩放比例容器
		.DPIScaler(UIScaler)	// 配置缩放比例
		[
			SNew(SOverlay)		// 添加 SOverlay,这是个可以放置多个子层级的容器型控件
			+SOverlay::Slot()	// 添加插槽(子控件)
			.HAlign(HAlign_Fill)	// 水平平铺
			.VAlign(VAlign_Fill)	// 竖直平铺
			[
				SNew(SImage)
				.Image(&MenuStyle->MenuHUDBackgroundBrush)
			]
	
			+SOverlay::Slot()
			.HAlign(HAlign_Center)	// 水平居中
			.VAlign(VAlign_Center)	// 竖直居中
			[
				SNew(SImage)
				// 使用样式类中为菜单栏界面准备的笔刷,刚刚在样式类里添加了
				.Image(&MenuStyle->MenuBackgroundBrush)	
			]
		]
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

// 补全方法1
float SSlAiMenuHUDWidget::GetUIScaler() const
{
	return GetViewportSize().Y / 1080.f;	// 返回计算后的缩放比例
}

// 补全方法2
FVector2D SSlAiMenuHUDWidget::GetViewportSize() const
{
	FVector2D Result(1920, 1080);	// 定义一个默认值
	if (GEngine && GEngine->GameViewport)
		GEngine->GameViewport->GetViewportSize(Result);
	return Result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

在 BPSlAiMenuStyle 里给 MenuBackgroundBrush 配置图片引用为 Menu_BIG。尺寸设置为 500 × 600。

随后在 Edit -> Project Setting -> User Interface 更改默认 DPI 缩放规则如下:(DPI Curve 这里只留下 scale 为 1 的点)

在这里插入图片描述

运行可以看到中间的菜单栏背景图会随着窗口尺寸的改变而正确缩放。

关于本集所说的 DPI 相关知识,笔者目前也找不到比较贴合本集课程的的文章可供参考,可以看下这篇:

《UE4 dpi scale 研究总结》

关于 UMG 的一些 UI 控件,推荐是跟着B站上的一些 UMG 教程进行学习,在学会使用 UMG 后再回来看 Slate 会容易理解很多。

4. 菜单布局实现

如何通过按钮改变界面控件的属性

首先需要获取要改变的控件,然后将包含更改逻辑的方法绑定到按钮的点击响应方法就可以了,具体操作如下。

SSlAiMenuHUDWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
// 把 .cpp 那边的 Overlay 头文件移过来,因为变量要用到(此处只是演示功能要用到,
// 后面可以把这个头文件移回去)
#include "SOverlay.h"

class SLAICOURSE_API SSlAiMenuHUDWidget : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlAiMenuHUDWidget)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:
	float GetUIScaler() const;
	FVector2D GetViewportSize();
	// 按钮响应方法
	FReply OnClick();

private:
	const struct FSlAiMenuStyle* MenuStyle;
	TAttribute<float> UIScaler;
	// 用于获取 Image 的 Slot 的变量
	SOverlay::FOverlaySlot* ImageSlot;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

SSlAiMenuHUDWidget.cpp

#include "UI/Widget/SSlAiMenuHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"	
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "Engine/Engine.h"
#include "SDPIScaler.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuHUDWidget::Construct(const FArguments& InArgs)
{
	MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
	UIScaler.Bind(this, &SSlAiMenuHUDWidget::GetUIScaler);

	ChildSlot
	[
		SNew(SDPIScaler)
		.DPIScaler(UIScaler)
		[
			SNew(SOverlay)
			+SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			[
				SNew(SImage)
				.Image(&MenuStyle->MenuHUDBackgroundBrush)
			]
	
			+SOverlay::Slot()
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			.Expose(ImageSlot)	// 暴露菜单子界面所在的这个 Slot 给 ImageSlot 变量
			[
				SNew(SImage)
				.Image(&MenuStyle->MenuBackgroundBrush)	
			]
			
			// 创建一个 Button 在界面上
			+SOverlay::Slot()
			.HAlign(HAlign_Left)	// 水平居左
			.VAlign(VAlign_Top)		// 竖直居上
			[
				SNew(SButton)
				.OnClicked(this, &SSlAiMenuHUDWidget::OnClick)	// 将按钮按下的方法跟自己的按钮响应方法绑定起来
			]
		]
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

// ...省略之前的两个方法的实现

// 实现按钮响应方法
FReply SSlAiMenuHUDWidget::OnClick()
{
	ImageSlot->HAlign(HAlign_Right).VAlign(VAlign_Bottom);	// 使菜单子界面所在的 Overlay 位置居右下
	return FReply::Handled();	// 返回一个已处理的信号
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

此时按左上角的按钮,中间的菜单图片就会从中间移到右下角。

完善菜单界面

SSlAIMenuHUDWidget 只是菜单页面的背景板,SSlAIMenuWidget 才是菜单栏的本体。

下面先补充完背景板页面。

SSlAiMenuHUDWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"

class SLAICOURSE_API SSlAiMenuHUDWidget : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlAiMenuHUDWidget)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:
	float GetUIScaler() const;
	FVector2D GetViewportSize() const;

private:

	const struct FSlAiMenuStyle* MenuStyle;
	TAttribute<float> UIScaler;
	// 菜单栏的指针
	TSharedPtr<class SSlAiMenuWidget> MenuWidget;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

SSlAiMenuHUDWidget.cpp

#include "UI/Widget/SSlAiMenuHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"
#include "SOverlay.h"
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "Engine/Engine.h"
#include "SDPIScaler.h"
#include "SSlAiMenuWidget.h"	// 引入菜单栏头文件

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuHUDWidget::Construct(const FArguments& InArgs)
{
	MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
	UIScaler.Bind(this, &SSlAiMenuHUDWidget::GetUIScaler);

	ChildSlot
	[
		SNew(SDPIScaler)
		.DPIScale(UIScaler)
		[		
			SNew(SOverlay)

			+SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			[		
				SNew(SImage)
				.Image(&MenuStyle->MenuHUDBackgroundBrush)
			]

			+SOverlay::Slot()
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			[
				SAssignNew(MenuWidget, SSlAiMenuWidget)	// 添加菜单栏Widget到界面并赋值给指针
			]
		]
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
// ... 两个方法省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

到这里,背景板 Widget 已经写完了,接下来是菜单栏 Widget。

首先,给样式类里面添加一些菜单栏 Widget 要用到的内容。

SlAiMenuStyle.h

USTRUCT()
struct SLAICOURSE_API FSlAiMenuStyle : public FSlateWidgetStyle
{
	

	// Menu 左图标的笔刷
	UPROPERTY(EditAnywhere, Category = "Menu")
	FSlateBrush LeftIconBrush;

	// Menu 右图标的笔刷
	UPROPERTY(EditAnywhere, Category = "Menu")
	FSlateBrush RightIconBrush;

	// 标题 Border 的笔刷
	UPROPERTY(EditAnywhere, Category = "Menu")
	FSlateBrush TitleBorderBrush;

};

// ... 略过此处类的定义

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

随后给菜单栏添加一些相应的控件。同样,这里也要用到样式类,所以要获取它。

SSlAiMenuWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"

class SBox;			// 提前告知编译器,SBox 是一个类
class STextBlock;	// 同上

class SLAICOURSE_API SSlAiMenuWidget : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlAiMenuWidget)
	{}

	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:

	// 用于保存根节点 SBox 的指针
	TSharedPtr<SBox> RootSizeBox;
	// 获取 Menu 样式
	const struct FSlAiMenuStyle* MenuStyle;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

SSlAiMenuWidget.cpp

#include "UI/Widget/SSlAiMenuWidget.h"
#include "SlateOptMacros.h"
#include "SlAiStyle.h"
#include "SlAiMenuWidgetStyle.h"
#include "SBox.h"		// 引入 SBox,也就是 UMG 的 SizeBox
#include "SImage.h"		// 图片控件
#include "STextBlock.h"		// 文本控件

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiMenuWidget::Construct(const FArguments& InArgs)
{
	// 获取 Menu 样式
	MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");

	ChildSlot
	[
		// 将 SBox 添加进界面,用于限定菜单栏尺寸(SBox/SizeBox 只能有一个子插槽)
		SAssignNew(RootSizeBox, SBox)
		[
			SNew(SOverlay)

			+SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			.Padding(FMargin(0.f, 50.f, 0.f, 0.f))	// 控件的间隔,4个数值分别是左上右下
			[
				SNew(SImage)
				.Image(&MenuStyle->MenuBackgroundBrush)	// 添加菜单栏的背景笔刷
			]

			+SOverlay::Slot()
			.HAlign(HAlign_Left)
			.VAlign(VAlign_Center)
			.Padding(FMargin(0.f, 25.f, 0.f, 0.f))
			[
				SNew(SImage)
				.Image(&MenuStyle->LeftIconBrush)	// 引用左图标笔刷
			]

			+SOverlay::Slot()
			.HAlign(HAlign_Right)
			.VAlign(VAlign_Center)
			.Padding(FMargin(0.f, 25.f, 0.f, 0.f))
			[
				SNew(SImage)
				.Image(&MenuStyle->RightIconBrush)	// 引用右图标笔刷
			]

			+SOverlay::Slot()
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Top)
			[
				SNew(SBox)
				// 给标题处的 SizeBox 设置长和宽
				.WidthOverride(400.f)
				.HeightOverride(100.f)
				[
					// 将 Border 添加进 SizeBox,Border 也是一个单插槽容器
					// 它和 SizeBox 的区别是,它可以添加图片背景,SizeBox 只是用于限定控件尺寸
					SNew(SBorder)
					.BorderImage(&MenuStyle->TitleBorderBrush)
					.HAlign(HAlign_Center)
					.VAlign(VAlign_Center)
					[
						// 添加文本控件,用于表示标题
						SAssignNew(TitleText, STextBlock)
						// 此处用到了老师在第3集《使用 WidgetStyle 设置样式》的末尾往样式获取类
						// 里设置的一个字体样式,如果跟老师的引擎版本不一样的话按老师的代码来,
						// 一般会显示乱码文字,因为没有获取到样式
						.Font(SlAiStyle::Get().GetFontStyle("MenuItemFont"))
						// 正常情况下“哈哈”会变成4个问号,因为字体目前不支持中文显示
						.Text(FText::FromString("I am 哈哈"))
					]
				]
			]
		]
	];
	
	// 给根节点的 SizeBox 设置长和宽
	RootSizeBox->SetWidthOverride(600.f);
	RootSizeBox->SetHeightOverride(510.f);
	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

在样式类蓝图中配置笔刷如下:

笔刷配置
如果标题显示 I am ???? 则说明没问题。显示中文的方式会在下一节课讲解。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号