【导图】:
【前言】:
奈何水平有限,本篇有多处扯淡的环节,谨慎参考。
【1.】:视频教程
迪迪老湿哪——UE4纯C++与Slate开发沙盒游戏ke.qq.com
【2.】:UE4 C++基础系列
- 【UE4】C++基础【00】——通俗易懂 用蓝图来学习 C++ 基础知识
- 【UE4】C++基础【01】——默认代码扒拉拉(初学者向)
- 【UE4】C++基础【02】——添加Slate至窗口
【3.】:UI实现原理:
【4.】:常混淆关键词梳理
常混淆的关键词,Slate、UMG、UI、HUD、Widget,前言中先梳理一下。
- Slate ——
- 定义——Slate是完全自定义、与平台无关的用户界面框架。简而言之, Slate是跨平台的UI框架 。
- 功能实现——
- 可以用来做应用程序Application的UI(如UE4 Editor 独立exe可执行程序)
- 工具架Toolbar的UI
- 更可以做游戏当中的UI (Slate代码创建 VS UMG可视化创建)
- UMG(Unreal Motion Graphics UI Designer) ——它是 工具 ,一个可视化的UI创建工具,就是WidgetBlueprint的那个Designer环境(Designer负责可视化、Graph负责控件事件定义)
- 作用——可用来创建UI元素,如HUD、菜单等
- 核心——Widgets控件
- UI(UserInterface) ——用户界面
- 含义——菜单和其他互动元素,像Menu主菜单、Pause暂停菜单、NPC头顶的对话框等。
- HUD(Head Up Display) ——
- 典故——来源于飞机飞行员,指抬头就能看到仪表盘上飞机的飞行数据。
- 含义——游戏期间(Runtime)在屏幕上覆盖的状态和信息
- 目的——告知玩家当前游戏状态,如分数、生命值、游戏时间等。
- 定义——HUD通常不可互动,意味着玩家不能单击HUD的元素
- Widget ——控件
- 含义 ——它们是一系列预先制作的函数
- 作用 ——可用于构建界面(如Button按钮、Slider滑块、ProgressBar进度条、Vertical/Horizontal Box 水平/竖直框。
- 大环境 ——这些Widget控件在专门的控件蓝图(UserInterface-> WidgetBlueprint )中编辑。它使用两个选项卡进行构造
- Designer ——控件可视化布局,位置、旋转、缩放、父子等
- Graph ——实现控件背后的功能,如细节面板中的Events事件,像Button的OnClicked、Hovered事件等。
【5.】:小贴士
我感觉初学者学C++就是个先不断找寻
对应英文语句和各种符号的意思的过程,跟阅读理解差不多。
我觉得学代码应该分为这些阶段:
- 新手
- Happiness——知道这么写能不报错,哈哈哈哈,有一定的奖励反馈以支撑头发继续战斗下去。(What?)
- Effect——简单知道这么写能起什么作用(How?)
- 高手
- Benifit——深入理解引擎为什么要这么写,有什么好处。(Why?)
在学习期间肯定有很多不会的地方,可以按以下方式查询。
- VS中鼠标悬浮语句出Tooltip提示框快速查看对应意思。
2.网页端虚幻官方C++ API Reference (或像上面鼠标Hover的时候,点击SearchOnline,会自动用Bing搜索关键词,跟内容浏览器资源右键View Documentation/See Full Documentation差不多)
3.深入了解的话,就右键PeekDefinations在当前文档内出小窗口快速浏览或 GoToDefinition到源文件查看相应代码。
一、 预备工作
【1.1】创建基础C++类(GameMode、PlayerController、HUD)
- 新建地图—— Content->Map->新建
MenuMap
- 设置项目默认地图——Project Settings ->Maps & Modes 设置
为项目默认地图MenuMap
- 新建C++ GameModeBase 为
ShitGameMode
- 确保 Public—— 头文件public、源文件private
- Public路径后添加
以将这个/GamePlay
放在 GamePlay文件夹下,规整一点(如果报错不能生成文件,后期VS处理)ShitGameMode
4. 新建 PlayerController 为
ShitController
,同样Public
5. 新建 HUD (跟UI一样放在屏幕上,但是不用手动AddToViewport)为
ShitHUD
,同样为Public。
- GameController
- GameMode
- HUD
如若出现报错,请按照提示在cpp中添加文件路径前缀:UE4添加C++类失败 如何解决?
【1.2】世界设置GameMode为新创建自定义的GameMode类
- ShitGameMode
二、VS代码编辑器配置GameMode
【2.1】给ProjectName.Build.cs添加Slate、SlateCore模块
当然内部原理更复杂,我等菜鸟简单了解一下具体的功能即可,后期深入再理解原理。
- Unreal Build Tool (UBT)——负责 各个模块的编译 并处理 各模块之间的依赖关系,Build.cs和Target.cs就是服务于UBT的。Build.cs中包含了定义的模块的依赖关系、额外的库、路径等信息,这些文件被编译成dll动态链接库文件,并通过单一的exe可执行文件进行加载。
- Unreal Header Tool (UHT)——C++代码解析工具
- 搜集带U的,如UCLASS()、UPROPERTY() 生成反射数据并注入到 GENERATED_BODY()中。
篮子悠悠:UE4 C++基础教程 - 工程目录结构zhuanlan.zhihu.com
如果我们要使用Slate UI 我们只需要Uncomment取消注解就可以了。
新添加格式为字符串,名字为Slate、SlateCore,到 私有从属组块名中。
PrivateSlate语句行,
Ctrl+K,Ctrl+U(Uncomment)(Ctrl+C(comment))
PrivateDependencyModuleNames.AddRange(new string[] {"Slate"、"SlateCore"});
【2.2】使用VAssistX插件在Gamemode.h头文件中新Declare构造函数
- VAssistX
- 快速追踪,
- Alt+G ——Declaration和Definition的快速跳转;
- Alt+O ——cpp 和 h 文件快速切换
- 快速创建,添加成员变量的Set/Get方法,如构造函数,CreateImplementation
- Alt+C (自定义)——CreateImplementation 快速创建实现,直接在头文件函数声明(Declaration)处快捷键,然后就跳转到了源文件中,并自动写好函数实现语句(Definition)。
- 快速追踪,
【2.3】 GENERATED_BODY()
和 GENERATED_UCLASS_BODY()
的区别
GENERATED_BODY()
GENERATED_UCLASS_BODY()
-
_Body()
- 标识的类的成员默认是Private的
- 标识的类 需要声明 无参数的构造函数 (Public:如AxxxActor)
- _
UCLASS_Body()
- 可以不声明构造函数(如果要实现构造函数需要加上
参数 (ObjectInitializer,对象构造器/对象初始化操作)const FObjectInitializer&ObjectInitializer:Super(ObjectInitializer)
- 可以不声明构造函数(如果要实现构造函数需要加上
我们之前学过Super()的作用就是调用这个函数之前会先调用父类中的函数。
默认的是GENERATED_BODY()
,本节只是简单了解一下二者区别。 【2.4】GameMode源文件中#include HUD和Controller的头文件
- 我们之前说过h是领导,外面接活,就是#include一堆引擎的东西,啥框架、反射生成啦这些玩意儿。
- cpp是员工,给领导干活,#include一堆我们自己创建的领导。
如Gamemode cpp员工要#include自身Gamemode领导,还有其他两个领导。注意路径,Gameplay或UI文件夹,否则 #include下面一堆姨妈红。
//ShitGameMode.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GamePlay/ShitGameMode.h"
#include "GamePlay/ShitController.h"
#include "UI/HUD/ShitHUD.h"
AShitGameMode::AShitGameMode()
{
}
【2.5】GameMode cpp 源文件中 补充构造函数定义,细化GameMode配置
说明:
-
作用域限定符(范围解析运算符) 。类外实现函数定义 (如蓝图学习C++ 【8.1】中类成员函数的类内类外定义 和【8.2】中构造函数的声明与定义)::
- StaticClass() 是UE4引擎自己通过反射创建的文件下面的一个函数,运行时可以返回这个类的UClass对象。
- 语法:类名Class=继承类名::StaticClass();
- 如 HUDClass=AShitHUD::StaticClass();
代码中的
=
等号其实就跟在编辑器ComboBox中选枚举一样,像完善GameMode详细参数,如HUD、Controller这些。我们在代码中直接写死,不让在编辑器中枚举选了。
// ShitGameMode.cpp
#include "GamePlay/ShitGameMode.h"
#include "GamePlay/ShitController.h"
#include "UI/HUD/ShitHUD.h"
AShitGameMode::AShitGameMode()
{
PlayerControllerClass = AShitController::StaticClass();
HUDClass = AShitHUD::StaticClass();
}
【2.6】Build检验
编辑器中看到在VS中定义的GameMode的属性都传过来了,且不可改动
三、VS代码编辑器配置PlayerController
【3.1】ShitController 构造函数定义声明
- 来到ShitController 头文件。
- 跟GameMode一样头文件中public声明构造函数
- 再鼠标悬浮构造函数处 Alt+C通过VAssistX插件在cpp中快速定义构造函数基本语法,即CreateImplementation创建声明的实现。
// ShitController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ShitController.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitController : public APlayerController
{
GENERATED_BODY()
public:AShitController(); //在此Alt+C
};
//ShitController.cpp
#include "GamePlay/ShitController.h"
AShitController::AShitController() //Alt+C 结果
{
}
【3.2】ShitController中详细定义函数
【1】
bShowMouseCursor=true;
显示鼠标
- 蓝图一般是关卡蓝图中getcontroller、ShowMouse变量、打勾不打勾 或PlayerController蓝图中勾选显示鼠标。( 构造函数中写的这个就跟Controller蓝图中Class Settings勾选是一样的)
- C++直接b布尔类型变量、ShowMouse变量、=True/False 是否启用
【2】设置只接受鼠标输入并将鼠标锁在窗口内
- 头文件中跟默认代码一样声明 BeginPlay初始函数 虚函数——
virtual void BeginPlay() override;
- 一样Alt+C CreateImplementation创建函数声明。
- 源文件中定义锁定鼠标在窗口内
- FInputModeUIOnly—— 用于设置仅允许UI响应用户输入的输入模式的数据结构
// Fill out your copyright notice in the Description page of Project Settings.
#include "GamePlay/ShitController.h"
AShitController::AShitController()
{
bShowMouseCursor = true;
}
void AShitController::BeginPlay()
{
FInputModeUIOnly InputMode;
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
SetInputMode(InputMode);
}
【3.3】Build检验
- VS Game Build
- Hot 热更新过来可以看到定义的Class都过来了
然后Standalone Game独立游戏模式 Alt+P 运行检验结果:
看到鼠标是显示出来的且卡在窗口内。四、VS代码编辑器配置SlateWidget
【4.1】新建两个SlateWidget
【操作】: 【注意事项】:- 需要注意的是SlateWidget并不会在ContentBrowser中出现,包括文件夹,它只会在VS中出现。(我还各种Build、Rebuild、重启测试,结果是真的没有, 大家别费那个劲儿在Content Browser中找了 )
- UE4 C++基础 篇中我们讲到过UE4采用Pascal命名法,S前缀代表SlateWidget。比如我们蓝图创建了一个名为ShitWidget的SlateWidget,VS中会变成SSlateWidget.h/.cpp。
- HUD是不带S前缀的,SlateWidget才带S前缀,注意一下。
【4.2】HUD基础代码配置
- 跟上面GameMode和Controller一样,在HUD 头文件中同样public声明,Alt+C源文件定义构造函数。
- ShitHUD cpp源文件中#include 引用
- SShitMenuHUDWidget.h头文件。
- SlateBasics.h头文件。(否则会报错)
// ShitHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShitHUD.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitHUD : public AHUD
{
GENERATED_BODY()
public:
AShitHUD(); //声明构造函数
}
//ShitHUD.cpp
#include "UI/HUD/ShitHUD.h"
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SlateBasics.h"
AShitHUD::AShitHUD()
{ };
【4.3】绑定Widget至HUD并添加到GameViewport游戏窗口中。
参考官方文档——在游戏中使用Slate
【1.】:头文件声明指针
// ShitHUD.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "ShitHUD.generated.h"
/**
*
*/
UCLASS()
class TESTGAME_API AShitHUD : public AHUD
{
GENERATED_BODY()
public:
AShitHUD();
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
};
上面的
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
跟下面的蓝图差不多。
- TSharedPtr 声明共享指针。
- 指针类为
(用UMG创建的自定义UI,继承自UserWidget)ShitMenuHUDWidget
- 指针名为
MenuHUDWidget
- TSharedPtr——等同于在蓝图细节面板中的变量栏创建一个变量(指针变量)
- ToSharedRef()——将指针指的Class类调用过来。
源文件定义指针
// ShitHUD.cpp
#include "UI/HUD/ShitHUD.h"
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SlateBasics.h"
AShitHUD::AShitHUD()
{
if (GEngine && GEngine->GameViewport)
{
SAssignNew(MenuHUDWidget, SShitMenuHUDWidget);
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef())
);
}
}
【解释说明】: - 【1】if语句
- && 逻辑与运算符,两个表达式必须都为true,整个表达式才为true
- 判断GEngine和GameViewport是否存在,存在则执行括号中的语句
- 【2】创建SlateWidget语句
- SAssignNew为SlateWidget的一种创建方式,要 搭配头文件中声明的指针 一起才管用。
TSharedPtr<class SShitMenuHUDWidget>MenuHUDWidget;
SAssignNew(MenuHUDWidget, SShitMenuHUDWidget);
两种创建方式的区别是:
- SNew(xxx)
- SAssign(指针,SlateWidget类名)
SNew和SAssignNew
- 【 3】添加到游戏窗口语句
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef())
);
-
添加到游戏窗口(默认代码 中我们还学过GEngine也是可以在屏幕上Print String打印信息)GEngine->GameViewport->AddViewportWidgetContent();
- 为了在游戏中显示一个Slate控件,就必须把这个控件添加到游戏视口(Viewport)中。
- 重叠的控件会按照Z-Order索引进行排序,大的数置顶在上面,小的数在下面,跟PS的图层一样。
- 游戏视口GameViewport是GameViewportClient的一个实例,可通过使用到游戏当前 U Engine实例的“ G Engine”(Game Engine?)指针访问——
GEngine->GameViewport
- GameViewport的AddViewportWidgetContent函数可以将控件添加到视口中。
SNew(SWeakWidget).PossiblyNullContent(MenuHUDWidget.ToSharedRef());
将上面通过SAssignNew创建的SlateWidget添加到视口中 【实现流程】: - PossiblyNullContent(MenuHUDWidget.ToSharedRef()——如果这个指针指向的widget类存在,那就
- SNew(SWeakWidget)——把它传到这个新创建的SWeakWidget SlateWidget中
- GEngine->GameViewport->AddViewportWidgetContent(....),将SWeakWidget添加到屏幕上。
- SWeakWidget 是什么鬼?——实现一个小部件,该小部件持有指向一个子小部件的弱指针(Weak Pointer)。它封装一段内容而不拥有它。 可以理解为一种绿绿的暧昧的关系。
- Tooltip=A,女一号
- Hovered Widget =B,男一号。
- Floating Window=C,男二号。
(哈哈,就好比男一号心里有女一号,然而女一号要通过跟男二号潜规则才能上位亮相,但是男二号心里并没有女一号。那是因为男二号还有很多可潜规则的对象。)
【3.】SlateWidget分辨- 项目编辑器中 ——
- 我们在项目编辑器中创建的是 两个特定SlateWidget类 ,这里SlateWidgets就代表添加到屏幕上面的那些UserWidget(蓝图)。
- VS中 ——
- 而我们在HUD中通过
创建SlateWidgets,指定我们创建的SlateWidgets以填充至屏幕中。SNew()或SAssignNew()
- 在 两个特定SlateWidgets类 中我们同样也可以通过
来创建SNew()或SAssignNew()
组件,以在Widgets中添加Button按钮。SButton
- 而我们在HUD中通过
【4.4】: 解决小Bug
虚幻的Bug真神奇:
- 有这种白色棱形问号的,不用思考,肯定是中文的问题,系统语言更改真的有时候能够解决很多问题的,比如微信定位地址,电脑系统英文会默认用Google打开,中文会默认用腾讯地图打开。 系统语言改为英文后,就到了第二个编号了,明显少了一个bug。
- 第二个它提示不能够删除dll动态链接库,直接关闭VS,然后在ContentBrowser中随便打开一个Class,然后再Build,就好了,编译成功!虚幻就是这么地神奇!
【4.5】SlateWidget中引入SButton组件
ShitMenuHUDWidget 源文件中新引入
SButton.h"
,添加SButton组件。
- [ ]中括号是重载运算符
- ChildSlot就是槽,可以放子控件。
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/Widget/SShitMenuHUDWidget.h"
#include "SButton.h"
#include "SlateOptMacros.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SShitMenuHUDWidget::Construct(const FArguments& InArgs)
{
ChildSlot
[
SNew(SButton)// 创建一个Button Slate
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
注意一下这个路径的问题,开头我是直接 在HUD cpp源文件中
#include"SButton.h"
,后来一直报错,结果检查到还是路径的问题呢。个人觉得Build.cs中引入模块名其实就跟文件夹定位的意思差不多,Build.cs定一次,一劳永逸,省得在自定义类的源文件中再多次#include这个文件夹了。
- 然后VS中右键SButton.h找到它的定义,然后再文件资源管理器中打开,查看路径。
所以最终
#include"Widgets/Input/SButton.h"
就解决问题了,因为路径对了嘛,UE可以找到SButton啦。
最终效果:可以看到Button充满了整个屏幕。不过我们还没有定义事件,我们下节再定义。
待续。添加样式啥的。