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

前言:

笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个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:
	//上半身的Montage
	UAnimMontage *PlayerHitMontage;
	UAnimMontage *PlayerEatMontage;
	UAnimMontage *PlayerFightMontage;
	UAnimMontage *PlayerPunchMontage;
	UAnimMontage *PlayerPickUpMontage;
	//保存当前播放的Montage
	UAnimMontage *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到viewport
	if (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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值