[UE4入门笔记(9)] 28.UE4断言 29.角色Montage动画播放 30.快捷栏UI --梁迪老师UE4纯C++Slate开发沙盒游戏

目录

        • 前言:
  • 本篇学习内容:
    • 28.UE4断言
    • 29.角色Montage动画播放
    • 30.快捷栏UI

前言:

笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个OpenCV学习笔记专栏已完结,在学习完OpenCV后,我继续学习C++,并用纯C++做UE4项目的方式继续提升自己的水平。

梁迪老师的水平非常高,他的课程本来也无需笔记:课程本身即为最好的笔记。但由于我天赋有限,还是边看边记,以防遗忘——知识点太多,步骤太繁杂了。在学习过程中,我也偶有思考,思索为什么某个方法老师要这样做。所以,一是为了记录,二是为了分享,才有了这个专栏。

内容方面,由于我在开启这个专栏时,此项目已经做完很多了。所以,前期的一些大篇幅叙述的知识,可能在后期应用中一带而过。以及,前期的一些知识,后期会重新剖析,并加上我的个人理解。

另外,若有学术交流/学业交流意愿,可以邮件联系1246210283@qq.com,希望一齐进步。


本篇学习内容:

28.UE4断言
29.角色Montage动画播放
30.快捷栏UI


28.UE4断言

参考文档:https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/ProgrammingWithCPP/Assertions/

在C和C++编程中,assert 可在开发期间帮助检测和诊断不正常或无效的运行时条件。这些条件通常检查是否指针为非空、除数为非零、函数并非递归运行,或代码要求的其他重要假设。但每次检查会使得效率十分低下。某些情况下,assert 会在延迟崩溃发生之前发现导致该崩溃的bug,例如删除未来tick所需的对象,协助开发人员发现引起崩溃的根本原因。assert 的关键特性之一是不存在于发布代码中,这意味着不但不会影响发布产品的性能,也没有任何副作用。对 assert 最简单的理解就是:"断言"必须一律为true,否则程序会停止运行。

虚幻引擎4(UE4)提供 assert 等同项的三个不同族系:check、verify 和 ensure。若要检查这些功能背后的代码,可在 Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h 中找到相关的宏。各个功能的行为略有不同,但它们都是开发期间使用的诊断工具,目标大致相同。

Check族系最接近基础 assert,因为当第一个参数得出的值为false时,此族系的成员会停止执行,且默认不会在发布版本中运行。

在大部分版本中,Verify族系的行为与Check族系相同。但即便在禁用Check宏的版本中,Verify宏也会计算其表达式的值。这意味着仅当该表达式需要独立于诊断检查之外运行时,才应使用Verify宏。举例而言,若某个函数执行操作,然后返回 bool 来说明该操作是否成功,则应使用Verify而非Check来确保该操作成功。因为在发布版本中Verify将忽略返回值,但仍将执行操作。而Check在发布版本中根本不调用该函数,所以行为才会有所不同。

Ensure族系类似于Verify族系,但可在出现非致命错误时使用。这意味着,若Ensure宏的表达式计算得出的值为false,引擎将通知崩溃报告器,但仍会继续运行。为避免崩溃报告器收到太多通知,Ensure宏在每次引擎或编辑器会话中仅报告一次。若实际情况需要Ensure宏在每次表达式计算得值为false时都报告一次,则使用"Always"版本的宏。

29.角色Montage动画播放

在完成了人物移动后,需要添加上半身动作,这可以通过如下方法实现:

将状态机保存到一个缓存块中,创建2个一样“使用缓存姿势”,在其中一个连接蒙太奇Slot并设置SlotName为UpperBody,随后创建一个骨骼混合,添加一个元素,此元素的骨骼名就是蒙太奇中控制上半身的根骨骼。

在蓝图完成动作蓝图的设置后,还需要完成代码工作。

(1)首先,创建一个Montage动画的枚举类型:

//上半身动画状态
namespace EUpperBody
{enum Type{None,Punch,Hit,Fight,PickUp,Eat};
}

(2)在父类PlayerAnim中定义上半身的5个Montage指针,和一个保存当前播放的Montage的指针:

protected://上半身的MontageUAnimMontage *PlayerHitMontage;UAnimMontage *PlayerEatMontage;UAnimMontage *PlayerFightMontage;UAnimMontage *PlayerPunchMontage;UAnimMontage *PlayerPickUpMontage;//保存当前播放的MontageUAnimMontage *CurrentMontage;

(3)分别在FirstPlayerAnim和ThirdPlayerAnim中给这些指针赋值,即读取对应的第一人称、第三人称Montage资源。
(4)在Character中创建保存上半身动画状态的枚举类型

pubic://上半身动画状态EUpperBody::Type UpperType;

这样就可以在PlayerAnim中获取到Character的动画状态。

(5)准备好之后,就可以在PlayerAnim中定义动画更新函数了。

protected://更新动作virtual void UpdateMontage();
void USlAiPlayerAnim::UpdateMontage()
{//如果不存在直接返回,避免空指针产生中断if (!SPCharacter) return;//如果当前的动作没有停止,不更新动作if (!Montage_GetIsStopped(CurrentMontage)) return;switch (SPCharacter->UpperType){case EUpperBody::None://如果有哪个动作在播放if (CurrentMontage != nullptr) {Montage_Stop(0);//输入0表示停止全部的动作CurrentMontage = nullptr;}break;case EUpperBody::Punch:if (!Montage_IsPlaying(PlayerPunchMontage)) {Montage_Play(PlayerPunchMontage);CurrentMontage = PlayerPunchMontage;}break;...}
}

这里整个的逻辑是这样的:在触发按键事件后,将在Controller中更新Character的UpperType为相对应的枚举类型,随后PlayerAnim的帧更新函数每帧读取到Character的动画状态,并播放相应的动画。鼠标抬起后,也触发相应的按键事件,将UpperType设置为None。同时为了点击鼠标后要播放一次完整的动画,所以在当前的动作没有停止时,不会更新动作。

(6)然而,为了避免在第一人称、第三人称同时触发某个事件,我们需要设置一次只播放第一或第三人称的动画,并且在播放动画时不能切换视角。

一次只播放第一或第三人称的动画:

在PlayerAnim中:

	protected://指定自己的运行人称EGameViewMode::Type GameView;

在2个子类中分别初始化。
在UpdateMontage()中添加代码:

		//如果当前的人称状态和这个动作的不一致,直接返回if (SPCharacter->GameView != GameView) return;

在播放动画时不能切换视角:

在Character中:

	pubic://是否允许切换视角bool IsAllowSwitch;

在PlayerAnim中添加函数:

	protected://修改是否允许切换视角void AllowViewChange(bool IsAllow);

实现AllowViewChange函数:

	void USlAiPlayerAnim::AllowViewChange(bool IsAllow){if (!SPCharacter) return;SPCharacter->IsAllowSwitch = IsAllow;}

修改UpdateMontage函数:在适当地方调用AllowViewChange函数即可

写到这里有一个问题:我们是在哪里实例化FirstPlayerAnim和ThirdPlayerAnim的?是在动作蓝图创建时指定的。我们在创建动作蓝图时,指定了蓝图的父类为FirstPlayerAnim和ThirdPlayerAnim。

30.快捷栏UI

(1)创建一个Slate Wiegdt Style:GameWidgetStyle和两个Slate Widget:GameHUDWidget、ShortcutWidget

(2)在GameHUD中添加widget到viewport:
.h:

public:ASlAiGameHUD();
private:TSharedPtr<class SSlAiGameHUDWidget> GameHUDWidget;

.cpp:

ASlAiGameHUD::ASlAiGameHUD()
{//添加widget到viewportif (GEngine && GEngine->GameViewport) {SAssignNew(GameHUDWidget, SSlAiGameHUDWidget);GEngine->GameViewPort->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(GameHUDWidget.ToSharedRef()));}
}

(3)在GameHUDWidget中添加DPI缩放规则、以SAssignNew形式创建一个ShortcutWidget

(4)在ShortcutWidget中实现结构:
快捷栏UI
分为两行:第一行是文字,用于显示当前物体;第二行是9个Border,用于动态存放物体。
整个UI的底层是SBox,随后添加2个SOverlay。
由于文字和物体都是动态变化的,我们需要创建2个指针来维护。
而每一个Border由最外的边框、中间的物品图标、右下角的数量三部分组成,我们循环创建9个这样的Border,然后添加到网格组件SUniformGridPanel中。
(5)写InitializeContainer()函数,初始化快捷栏内容

(6)继续写JsonHandle类,用于读取物品属性表。和读取游戏设置时的写法相同,只是这次会把数据读取到一个TMap中。
以及,由于DataHandle的构造函数会在MenuMap调用,所以不能在构造函数里初始化这个TMap。要新建一个方法,让游戏跳转到GameMap场景时,调用DataHandle下的一个方法,来对这些数据实例化。

(7)在SlAiTypes中准备好一个快捷栏容器的结构体,结构体内有物品数量、编号,对应容器的三个指针,对应的笔刷,以及构造函数等函数。

(8)继续写InitializeContainer()函数,循环创建容器后实例化一个结构体,并添加到ContainerList


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部