斯坦福 UE4 C++ ActionRoguelike游戏实例教程 07.在C++中使用UMG
斯坦福 UE4 C++ ActionRoguelike游戏实例教程 07.在C++中使用UMG
斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论
概述
本篇文章的目标是创建一个基于C++的UMG类,并以这个类作为子类,为攻击到的敌对小兵添加一个血条UI。
最终效果如下:

目录
- 认识UMG
- 创建小兵血条
UMG
UE中的UMG(Unreal Motion Graphics)是一种用于创建用户界面(UI)和HUD(头部显示)的工具。UMG提供了一个可视化的编辑器,允许开发人员轻松创建复杂的UI和HUD,而无需编写代码。正如我们常见到的血条UI、操作提示、各种游戏内积分提示都是可以使用UMG实现的。

UMG包括一系列可自定义的预制件,如按钮、文本框、滑块、进度条等等。这些预制件可以通过拖放操作在编辑器中创建,然后进行定位、缩放和旋转等操作以满足特定的UI需求。此外,UMG还提供了脚本编写接口,使得开发人员可以通过编写脚本来实现更加复杂的UI逻辑。
UMG编辑界面就不详细展示了,这里简单提一下UMG的使用。

创建完成后,可以在任意事件蓝图里使用如下方式创建控件,并添加到视口。

今天还要介绍一种用c++创建和编辑UMG的方法,当然,最方便的还是直接使用UMG提供的编辑器来操作,我们用C++通常只是创建一个基类用来定义UMG蓝图的接口。具体怎么使用UMG,让我们边做边说。
创建小兵血条
编辑C++代码
UserWidget可以说是各种用户UI控件的基类,后续使用的蓝图类也是继承于该类。我们将他命名为SurWorldUserWidget,之后这个类还会作为其他控件的基类,大家在设计的时候可以好好考虑这个类里应该实现什么功能。

创建完成后,UE会自动在uproject文件里自动添加编译"UMG"模块,出于一致性,记得在Build.cs文件里添加"UMG"。
下面我贴出了新创建的类的所有代码。先看看头文件,里面定义了一些成员:
- 首先定义了一个SizeBox类型的成员。SizeBox允许使用者自定义其大小,并限定其内容的尺寸。注意这里使用了
meta = (BindWidget)修饰符,和名字一样,这是控件专用的修饰符,可以将指针绑定到UMG编辑器里同名同类型的组件。如果编辑器里没有对应组件,编译甚至还会报错。 - 作为小兵的血条控件,就需要绑定一个小兵的对象。这里定义了
AActor* AttachedActor,用意很好理解。值得一提的是,由于小兵和UMG控件是独立的两个对象,当小兵对象被销毁时,传统C++的方法是无法在控件类中得知指针是否被释放的,容易造成空悬指针的问题。好在UE引进了垃圾回收机制,只要加上UPROPERTY宏,就自动为成员变量登记垃圾回收。当指针所指的对象销毁掉后,会自动将指针置为nullptr,比C++11的share_ptr还好用( - 最后就是重载了NativeTick函数,每次Tick都会执行一次。
//SurWorldUserWidget.h
UCLASS()
class FPSPROJECT_API USurWorldUserWidget : public UUserWidget
{
GENERATED_BODY()
protected:
//该宏允许蓝图编辑器的同名同类型的组件 与 C++中的成员指针相关联
//因此要想成功绑定,蓝图里的控件必须是同名同类型的
UPROPERTY(meta = (BindWidget))
USizeBox* ParentSizeBox;
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
public:
//一个很重要的一点就是添加了UPROPERTY宏的对象都会被列入UOBJECT的垃圾回收系统管理。当这个对象在其他地方被释放(destroy)后,所有指向这个对象的指针都会被赋为NULL,有点像shared_ptr
UPROPERTY(BlueprintReadOnly, Category = "UI")
AActor* AttachedActor;
};
接下来是Cpp文件的内容。
具体来说,这段代码的作用如下:
- 首先,代码会调用Super::NativeTick(MyGeometry, InDeltaTime)来调用UserWidget类的NativeTick方法,以确保父类的功能正常工作。
- 然后,代码会检查目标Actor是否有效。正如上面所说的,垃圾回收机制解决了空悬指针的问题,但是没有解决空指针的问题。因此这里同样需要注意空指针的问题。这里使用了IsValid函数进行判断指针是否有效,该函数除了判断指针是否为空以外,还判断该对象是否被标记为删除,也就是调用了Destroy之类的函数。如果AttachedActor为空,则调用RemoveFromParent()从父级控件中移除自定义控件,并输出日志信息,以告知开发者该控件被移除。
- 接下来,代码会通过UGameplayStatics::ProjectWorldToScreen函数将AttachedActor的三维世界坐标投影到二维屏幕坐标系中,得到屏幕坐标。
- 接着,代码会使用UWidgetLayoutLibrary::GetViewportScale函数获取视口缩放比例,然后将屏幕坐标除以该比例,以确保在不同分辨率的屏幕上,控件的位置保持一致。
- 最后,如果ParentSizeBox不为空,代码会将控件的渲染位置设置为屏幕坐标,以实现控件位置的更新。
//SurWorldUserWidget.cpp
void USurWorldUserWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
//首先判断目标Actor是否有效
if(!IsValid(AttachedActor))
{
RemoveFromParent();
UE_LOG(LogTemp, Warning, TEXT("AttachedActor no longger valid, removeing Health Widget."));
return;
}
FVector2D ScreenPosition;
//该函数返回三维世界投影到二维平面上的坐标
if(UGameplayStatics::ProjectWorldToScreen(GetOwningPlayer(), AttachedActor->GetActorLocation(), ScreenPosition))
{
float Scale = UWidgetLayoutLibrary::GetViewportScale(GetWorld());
ScreenPosition /= Scale;
}
if(ParentSizeBox)
{
//设置其渲染到屏幕上的位置
ParentSizeBox->SetRenderTranslation(ScreenPosition);
}
}
编辑蓝图
虽然文章讲的是在C++中使用UMG,不得不说在代码编辑UI实在是非常低效的做法。通常蓝图和C++代码截图的正确姿势是使用C++定义类的功能和接口(demo),使用蓝图来做具体方法的实现。这里当然也不例外。
这里为刚才创建的类创建一个蓝图子类,将其命名为MinionHealth_Widget。

进入编辑器,如果点击编译会提示你需要绑定ParentSizeBox,这就刚才Meta修饰符的作用所在了。

将页面的布局修改成如图:

其中,画布画板允许其中的控件自由排布,非常适合我们根据自己的喜好手动布局。这时我们会发现SizeBox已经可以在界面中自由拖动了。将Image材质设置为前几节课制作的HealthBar,并给Image起一个自己看的顺眼的名。这里将ParentSizeBox设置成大小到内容:

该选项会使SizeBox的大小固定为其包含的组件的大小。这里的组件只有一张图片,即这张图片多大,SizeBox就有多大。
修改根据自己的喜好修改Image控件,主要是修改图像大小,由于大小到内容的作用,SizeBox会随着Image的大小变化而变化。

为小兵添加控件相关逻辑
代码如下。在.h文件中,添加了UMG控件的对象指针,代表着一个小兵对应一个空间。并定义了要生成空间的类型。
class FPSPROJECT_API ASurAiCharacter : public ACharacter
{
GENERATED_BODY()
......
protected:
USurWorldUserWidget* ActiveHealthBar;
//我们希望生成的UI类型
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UUserWidget> HealthBarWidgetClass;
......
}
在.cpp文件中,主要修改OnHealthChanged函数,因为我们希望血条UI只在小兵被攻击时才出现。为了防止每次攻击都会创建一个控件,这里判断控件指针是否为空来避免。
void ASurAiCharacter::OnHealthChanged(AActor* InstigatorActor, USurAttributeComponent* OwningComp, float NewHealth,
float Delta)
{
...
//保证只创建一次控件
if(ActiveHealthBar == nullptr)
{
ActiveHealthBar = CreateWidget<USurWorldUserWidget>(GetWorld(), HealthBarWidgetClass);
if(ActiveHealthBar)
{
ActiveHealthBar->AttachedActor = this;
ActiveHealthBar->AddToViewport();
}
}
...
}
效果如下,当我们击中小兵后,小兵身上会出现一个血条。

当前的血条位置不太对,我们可以在编辑器里对血条的位置进行调整,图中设置了血条的对其,将其都设置为0.5,这样血条就会在目标坐标的正中心出现。

为了抬高血条的高度,在SurWorldUserWidget基类里定义了控件生成的偏移,将该偏移量添加到SurWorldUserWidget.cpp的ProjectWorldToScreen函数调用即可。
//SurWorldUserWidget.h
UPROPERTY(EditAnywhere)
FVector WorldOffset;
将WorldOffset设置为(0,0,100),血条的位置如下(后文会讲解掉血效果)

添加掉血效果
正常来说做到这步,我们已经实现了一个悬浮在小兵头上的血条,他会在我们攻击小兵的时候出现。接下来该做掉血效果,该效果表现为血条的红色部分会随着小兵的血量减少而减少。让我们进入MinionHealth_Widget的图表中,从事件构造拉出一条线,为AttachedActor的属性组件绑定我们期望在生命值发生变化时产生的事件。

蓝图修改如下,我们获取了AttachedActor的属性组件,为其绑定了一个OnHealthChanged事件,这样,每当属性组件的血量变化时,都会通过委托机制调用这个事件函数。
在该事件函数中,我们获取了动态材质也就是M_HealthBar,就像之间编辑人物血条一样设置了M_HealthBar的ProgressAlpha变量,关于M_HealthBar是什么东西,既然都看到这里了,应该都挺熟悉前面的课程。请允许笔者偷个懒,不展开描述了。具体参考之前创建人物血条的课程Lecture 7。

最后的效果如下。我们发现当第一次攻击小兵时,他出现的血条是满血的状态,也就是出现了BUG。

检查代码逻辑发现,这是程序执行顺序导致的BUG。当我们攻击到小兵时,我们还未创建控件,还未将控件的OnhealthChanged事件绑定到属性组件中,因此本次血量变化的委托之中并没有控件的OnhealthChanged事件,所以血条就不会发生变化。
解决这个BUG的办法很简单,既然构造控件和掉血是同时发生的,那我们在创建控件后手动更新一下血条即可。具体做法就是在控件构造时主动调用一次OnhealthChanged事件。当然,你也可以自己定义一段逻辑来更新材质,道理都是一样的。

当小兵血条归零后,我们可以直接删除血条。这里也是添加了一小段逻辑,将其从父项中移除。在没有父控件的情况下,这里的父项指的就是玩家的视口。至于有父控件的情况会在后面的文章提到。

最终效果&总结
本篇文章我们在C++中创建了一个UMG组件的基类,结合蓝图创建了一个在小兵头上显示的血条。在之后的课程中,我们还将学习如何将这些一个一个的统合到一个总控件上。

参考链接
UE4 UMG的简单使用 https://zhuanlan.zhihu.com/p/461626363
斯坦福 UE4 C++ ActionRoguelike游戏实例教程 07.在C++中使用UMG的更多相关文章
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程07:UI》
概述: UI即User Interface(用户界面)的简称.UI设计是指对软件的燃机交互.操作逻辑.界面美观的整体设计.好的UI设计不仅可以让游戏变得更有品位,更吸引玩家,还能充分体现开发者对游戏整 ...
- Cocos2d-x3.0游戏实例《不要救我》第十篇(结束)——使用Json配置数据类型的怪物
如今我们有2种类型的怪物,并且创建的时候是写死在代码里的,这是要作死的节奏~ 所以.必须可配置.不然会累死人的. ; i < size; ++i) { int id = root[i][&quo ...
- Cocos2d-x3.0游戏实例之《别救我》第八篇——TiledMap实现关卡编辑器
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/musicvs/article/details/25368273 好吧.我真心全然搞不懂.我如今仅仅只 ...
- libgdx游戏引擎教程
第一讲:libgdx游戏引擎教程(一)性能优良的游戏引擎—libgdx http://www.apkbus.com/android-57355-1-1.html 第二讲: libgdx游戏引擎教程(二 ...
- 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇:简介及目录》(附上完整工程文件)
G-3D引擎2D射击类游戏制作教程 游戏类型: 打飞机游戏属于射击类游戏中的一种,可以划分为卷轴射击类游戏. 视觉表现类型为:2D 框架简介: Genesis-3D引擎不仅为开发者提供一个3D游戏制作 ...
- Python导出Excel为Lua/Json/Xml实例教程(一):初识Python
Python导出Excel为Lua/Json/Xml实例教程(一):初识Python 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出 ...
- Web 开发中应用 HTML5 技术的10个实例教程
HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...
- 值得 Web 开发人员收藏的20个 HTML5 实例教程
当开始学习如何创建 Web 应用程序或网站的时候,最流行的建议之一就是阅读教程,并付诸实践.也有大量的 Web 开发的书,但光有理论没有实际行动是无用的.现在由于网络的发展,我们有很多的工具可以用于创 ...
- 对《[Unity官方实例教程 秘密行动] Unity官方教程《秘密行动》(十二) 角色移动》的一些笔记和个人补充,解决角色在地形上移动时穿透问题。
这里素材全是网上找的. 教程看这里: [Unity官方实例教程 秘密行动] Unity官方教程<秘密行动>(九) 角色初始设定 一.模型设置: 1.首先设置模型的动作无限循环. 不设置的话 ...
- Cocos2d-x3.0游戏实例《不要救我》第一章——前言
我们可以学习? 这是一个非常easy游戏.但更多的东西用(对于初学者).至少,对于它的一个例子,有点多. 笨木头花心贡献.啥?花心?不呢.是用心~ 转载请注明,原文地址:http://www.benm ...
随机推荐
- 2023-10-07:用go语言,给定n个二维坐标,表示在二维平面的n个点, 坐标为double类型,精度最多小数点后两位, 希望在二维平面上画一个圆,圈住其中的k个点,其他的n-k个点都要在圆外。
2023-10-07:用go语言,给定n个二维坐标,表示在二维平面的n个点, 坐标为double类型,精度最多小数点后两位, 希望在二维平面上画一个圆,圈住其中的k个点,其他的n-k个点都要在圆外. ...
- ERROR: <bits/stdc++.h>, 'cstdalign' file not found, running C++17
Modified 1 year, 1 month ago Viewed 9k times 4 I'm trying to run a piece of code in Visual Studio Co ...
- 轻量通讯协议 --- MQTT
介绍 一.MQTT简介 MQTT(Message Queuing Telemetry Transport) 是一种轻量级的消息传输协议,通常用于在物联网(IoT)和传感器网络中进行通信.它设计用于在低 ...
- CF580B
题目简化和分析: 选择 \(n\) 个朋友,满足以下条件: 工资差异 \(<d\) 友谊和最大(题目翻译不太清楚) 现在面临两个问题 求差异值 求友谊和 所以我们理应想到线段树双指针. 排序后满 ...
- 前端web页面支持MQTT消息推送
MQTT服务一般用直接下载mosquitto,安装后启动服务即可.方便可靠. 但是默认情况下只开通了1883的tcp访问,用html的web页面上调用就不行了. 其实mosquitto是支持多端口的, ...
- Swagger系列:SpringBoot3.x中使用Knife4j
目录 一.简介 二.版本说明 三.使用 四.效果图 一.简介 官网:https://doc.xiaominfo.com/ Knife4j是一个集Swagger2 和 OpenAPI3 为一体的增强解决 ...
- Vue之基础事件
1.基础事件,先弹框试试 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- 广义 SAM 学习笔记
开 CF 开到了一道广义 SAM,决定来学一学. 发现网上确实充斥着各种各样的伪广义 SAM,也看到了前人反复修改假板子的过程,所以试着来整理一下这堆奇奇怪怪的问题. 当然本文的代码也不保证百分百正确 ...
- 定时重启Nginx、MySql等服务
利用 Linux Crontab,每天定时重启 Nginx.MySQL等服务. 命令行格式说明 f1 f2 f3 f4 f5 program 其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份 ...
- CF1523D Love-Hate 题解
抽象化题意: 一共有 \(m\) 个元素,给定 \(n\) 个集合,每个集合的元素不超过 \(15\) 个,求出一个元素个数最多的集合 \(S\) 是至少 \(\lceil \dfrac{n}{2} ...