《UE5_C++多人TPS完整教程》学习笔记53 ——《P54 转身(Turning in Place)》
本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P54 转身(Turning in Place)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
文章目录
- P54 转身(Turning in Place)
- 54.1 C++ 实现转身
- 54.2 站立转身蓝图
- 54.3 蹲伏转身蓝图
- 54.4 (补充)转身动画网络同步
- 54.5 Summary
P54 转身(Turning in Place)
本节课我们将在瞄准偏移的基础上实现人物角色的转身。
54.1 C++ 实现转身
-
我们希望我们的人物角色能够在摄像机旋转到一定角度时进行转身,播放相应的原地转身动画,这意味着人物角色的动画蓝图需要知道何时播放。我们可以通过创建一个记录不转身(Not turning)、左转(Turning left)、右转(Turning right)这三个状态的枚举值来实现。
-
打开 Visual Studio,新建 C++ 头文件 “
Turning.h
”,默认情况下,VS 会将其保存在 “...\Blaster\Intermiate\...
” 目录下,而这个文件夹经常被删除或被重新生成,因此我们将 “Turning.h
” 保存在新创建的目录 “...\Blaster\Source\Blaster\BlasterTypes
” 下,我们之后也会将其他类型的头文件放在这里。 -
重新生成 “
Blaster
” 项目文件,然后在 “Turning.h
” 中定义枚举类型 “ETurningInPlace
”,该类型下包含不转身(Not turning)、左转(Turning left)、右转(Turning right)三个枚举值。/* P54 转身(Turning in Place)*/ #pragma onceUENUM(BlueprintType) enum class ETurningInPlace : uint8 {ETIP_Left UMETA(DisplayName = "Turning Left"),ETIP_Right UMETA(DisplayName = "Turning Right"),ETIP_NotTurning UMETA(DisplayName = "Not Turning"),ETIP_MAX UMETA(DisplayName = "DefaultMax"), }; /* P54 转身(Turning in Place)*/
-
在 “
BlasterCharacter.h
” 中声明 “ETurningInPlace
” 枚举类型变量 “TurningInPlace
” 和实现人物角色转身的函数 “TurningInPlaceFunc()
”,然后定义内联函数 “GetTurningInPlace()
” 以便获取 “TurningInPlace
”。/*** BlasterCharacter.h ***/.../* P54 转身(Turning in Place)*/ #include "Blaster/BlasterTypes/TurningInPlace.h" /* P54 转身(Turning in Place)*/#include "BlasterCharacter.generated.h"UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...private:...float AO_Yaw; // 人物角色瞄准偏移偏航角float AO_Pitch; // 人物角色瞄准偏移俯仰角FRotator StartingAimRotation; // 人物角色的起始瞄准旋转/* P54 转身(Turning in Place)*/ETurningInPlace TurningInPlace;void TurningInPlaceFunc(float DeltaTime); // 实现人物角色原地转身/* P54 转身(Turning in Place)*/public: ...AWeapon* GetEquippedWeapon(); // 访问枪战组件,获取装备的武器/* P54 转身(Turning in Place)*/FORCEINLINE ETurningInPlace GetTurningInPlace() const { return TurningInPlace; }/* P54 转身(Turning in Place)*/ };
-
在 “
BlasterAnimInstance.h
” 中同样声明 “ETurningInPlace
” 枚举类型变量 “TurningInPlace
”,并在 “BlasterAnimInstance.cpp
” 的函数 “NativeUpdateAnimation()
” 中调用 “BlasterCharacter.h
” 定义的内联函数 “GetTurningInPlace()
” 获取人物角色的转身状态,存储在 “TurningInPlace
” 中。/*** BlasterAnimInstance.h ***/.../* P54 转身(Turning in Place)*/ #include "Blaster/BlasterTypes/TurningInPlace.h" /* P54 转身(Turning in Place)*/...UCLASS() class BLASTER_API UBlasterAnimInstance : public UAnimInstance {GENERATED_BODY()...private:...UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;FTransform LeftHandTransform; // 左持枪手变换/* P54 转身(Turning in Place)*/UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;ETurningInPlace TurningInPlace; // 原地转身状态/* P54 转身(Turning in Place)*/ };
/*** BlasterAnimInstance.cpp ***/...// 原生(Native)类更新函数 NativeUpdateAnimation() 覆写,用于在每一帧调用以更新动画 void UBlasterAnimInstance::NativeUpdateAnimation(float DeltaTime) {...bAiming = BlasterCharacter->IsAiming(); // 调用 BlasterCharacter 的 IsAiming() 函数来判断人物角色是否在瞄准/* P54 转身(Turning in Place)*/TurningInPlace = BlasterCharacter->GetTurningInPlace(); // 调用 BlasterCharacter 的 GetTurningInPlace() 函数来获取人物角色原地转身状态/* P54 转身(Turning in Place)*/...}
-
在 “
BlasterCharacter.cpp
” 的构造函数 “ABlasterCharacter()
” 中设置 “TurningInPlace
” 的默认枚举值为 “ETurningInPlace::ETIP_NotTurning
”,即人物角色的转身状态默认为不转身。接下来,添加瞄准偏移函数 “AimOffset()
” 中添加代码:当人物角色静止站立且不跳跃时调用人物角色原地转身的函数 “TurningInPlace()
”;当人物角色奔跑或跳跃时,设置 “TurningInPlace
” 的枚举值为 “ETurningInPlace::ETIP_NotTurning
”,从而禁用转身 。随后,完成 “TurningInPlace()
” 的定义,根据 “AO_Yaw
” 的值判断人物角色的转身状态,当 “AO_Yaw
” 大于 90.0 时,设置 “TurningInPlace
” 的枚举值为 “ETurningInPlace::ETIP_Right
”,从而实现向右转身,当 “AO_Yaw
” 小于 -90.0 时设置 “TurningInPlace
” 的枚举值为 “ETurningInPlace::ETIP_Left
”,从而实现向右转身。/*** BlasterCharacter.cpp ***/...// Sets default values ABlasterCharacter::ABlasterCharacter() {...GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); // 让人物角色的胶囊体组件忽略来自摄像机碰撞通道的所有检测GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); // 让人物角色的骨骼网格体组件忽略来自摄像机碰撞通道的所有检测/* P54 转身(Turning in Place)*/TurningInPlace = ETurningInPlace::ETIP_NotTurning; // 默认转身状态为不转身/* P54 转身(Turning in Place)*/ }...// 瞄准偏移 void ABlasterCharacter::AimOffset(float DeltaTime) {...if (Speed == 0.f && !bIsInAir) { // 当人物角色静止站立且不跳跃时FRotator CurrentAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f); // 获取人物角色当前瞄准旋转FRotator DeltaAimRotation = UKismetMathLibrary::NormalizedDeltaRotator(CurrentAimRotation, StartingAimRotation); // 标准化获取 CurrentAimRotation 和 StartingAimRotation 的差量AO_Yaw = DeltaAimRotation.Yaw; // 获取人物角色瞄准偏航角bUseControllerRotationYaw = false; // 禁用控制器旋转偏航/* P54 转身(Turning in Place)*/TurningInPlaceFunc(DeltaTime);/* P54 转身(Turning in Place)*/}if (Speed > 0.f || bIsInAir) { // 当奔跑或跳跃时StartingAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f); // 改变奔跑或跳跃状态转换为静止站立状态时的起始瞄准旋转AO_Yaw = 0.f; // 由于启用了控制器旋转偏航,人物角色朝向始终面向控制器当前朝向,因此设置 AO_Yaw 为 0 bUseControllerRotationYaw = true; // 启用控制器旋转偏航/* P54 转身(Turning in Place)*/TurningInPlace = ETurningInPlace::ETIP_NotTurning; // 禁用转身/* P54 转身(Turning in Place)*/}...}.../* P54 转身(Turning in Place)*/ // 实现人物角色原地转身 void ABlasterCharacter::TurningInPlaceFunc(float DeltaTime) {// 输出左右转动摄像机时 AO_Yaw 的值,可以观察到持续向右转动时 AO_Yaw 为更大的正值,持续向左转动时 AO_Yaw 为更小的负值// UE_LOG(LogTemp, Warning, TEXT("AO_Yaw: %f"), AO_Yaw);if (AO_Yaw > 90.f) {TurningInPlace = ETurningInPlace::ETIP_Right; // 向右转身} else if (AO_Yaw < -90.f) {TurningInPlace = ETurningInPlace::ETIP_Left; // 向左转身} } /* P54 转身(Turning in Place)*/...
54.2 站立转身蓝图
-
编译后,打开 “
BlasterAnimBP
” 的 “Equipped
” 状态机窗口,进入 “Standing
” 状态节点编辑器,从 “Idle
” 引出两个新的状态节点 “TurnLeft
” 和 “TurnRight
”。
-
绘制 “
TurnLeft
” 和 “TurnRight
” 状态节点蓝图。
-
在 “Idle到TurnLeft(规则)” 和 “Idle到TurnRight(规则)” 编辑界面添加 “等于(枚举)”(Equal(Enum))节点,绘制状态转换规则蓝图。
-
编译、保存后进行测试。 当我们转动摄像机视角至 “
AO_Yaw
” 的值大于 90.0 或小于 -90.0 时,可以观察到我们的人物角色会播放向右或向左转身的动画,但是在我们没右按下鼠标右键进行瞄准时,转身动画中却包含了瞄准姿势,因此需要修改 “TurnLeft
” 和 “TurnRight
” 状态节点蓝图。
-
由于在修改 “
TurnLeft
” 和 “TurnRight
” 蓝图时我们需要重复用到一些蓝图节点,涉及到多次复制粘贴操作,于是不如把这些节点整合到一个状态机中然后使用缓存姿势节点。在 “AnimGraph
” 中添加新的状态机节点 “Standing Idle
”,将其连接到 “新保存的缓存姿势” 节点 “Standing Idle
”。
-
编辑状态机 “
Standing Idle
” 以及状态节点 “Standing Idle
” 的蓝图。
-
在 “
TurnLeft
” 和 “TurnRight
” 状态节点蓝图中 “使用缓存姿势节点 Standing Ilde”,当人物角色进行转身时,只使用下半身的动画姿势。顺便再修改 “Idle
” 的状态节点蓝图。
-
编译、保存后进行测试,可以观察到我们的人物角色不在瞄准状态下进行转身时不再包含瞄准姿势。
54.3 蹲伏转身蓝图
-
我们想要在人物角色在蹲伏状态下也能进行原地转身。在 “
AnimGraph
” 中添加新的状态机节点 “Crouching Idle
”,将其连接到 “新保存的缓存姿势” 节点 “Crouching Idle
”;在状态机 “Crouching Idle
” 下新建状态节点 “Crouching Idle
”,并为其绘制蓝图。随后,“使用缓存姿势节点 Crouching Idle” 修改先前的状态节点 “CrouchingIdle
” 的蓝图。
-
仿照绘制站立转身蓝图的步骤,创建向右或向左蹲伏转身状态节点,构建蹲伏待机到转身转换规则,绘制蹲伏转身蓝图。
-
编译、保存后进行测试,可以观察到人物角色在蹲伏状态下也能进行转身,但是转身时右腿放置的位置不正确,直接悬空了。
-
打开向左转身的动画资产 “
Crouch_Turn_Left
” 编辑器,在第 0 帧暂停动画,选择右腿骨骼节点 “calf_r
”,将其向下旋转 20°,并为其添加关键帧;接着,通过当前动画的预览网格体创建新的向左转身动画资产 “CrouchTurn_L
”。随后,对向右转身的动画资产 “Crouch_Turn_Right
” 执行相同的操作,创建新的向右转身动画资产 “CrouchTurn_R
”。
-
将新的动画资产 “
CrouchTurn_L
” 和 “CrouchTurn_R
” 分别添加到状态节点 “TurnLeft
” 和 “TurnRight
” 的蓝图面板中,修改其蓝图。
 -
编译、保存后再进行测试,可以观察到人物角色在蹲伏转身时右腿放置的位置是贴近地面的。
54.4 (补充)转身动画网络同步
-
和 《UE5_C++多人TPS完整教程》学习笔记49 ——《P50 应用瞄准偏移(Applying Aim Offset)》 一样,教学视频在这里又没有进行相关属性的网络同步,如果我们操控客户端 1 上的人物角色进行转身,服务器上的人物角色可以同步地播放这个客户端所控制的人物角色的转身动画,但在客户端 2 上客户端 1 所控制的人物角色的转身动画不能同步进行播放;如果我们操控服务器上的人物角色进行转身,客户端 1 和 2 上服务器所控制的人物角色的转身动画不能同步进行播放。因此,我们需要自己手动去进行转身属性的网络同步。
-
在 “
BlasterCharacter.h
” 声明转身枚举类型 “TurningInPlace
” 为可复制变量,接着,在 “BlasterCharacter.cpp
” 的函数 “GetLifetimeReplicatedProps()
” 为 “TurningInPlace
” 注册生命周期,指明其复制条件为模拟端复制。/*** BlasterCharacter.h ***/...UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...private:...UPROPERTY(Replicated) // 可复制变量float AO_Yaw; // 人物角色瞄准偏移偏float AO_Pitch; // 人物角色瞄准偏移俯仰角UPROPERTY(Replicated) // 可复制变量FRotator StartingAimRotation; // 人物角色的起始瞄准旋转/*(补充)P54 转身(Turning in Place)*/UPROPERTY(Replicated) // 可复制变量ETurningInPlace TurningInPlace; // 人物角色的原地转身状态 /*(补充)P54 转身(Turning in Place)*/...};
/*** BlasterCharacter.cpp ***/...// 重写复制属性函数 void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {// 调用SuperSuper::GetLifetimeReplicatedProps(OutLifetimeProps);// 添加要为派生的类 ABlasterCharacter 复制的属性,需要添加头文件 "Net/UnrealNetwork.h"// DOREPLIFETIME(ABlasterCharacter, OverlappingWeapon); // DOREPLIFETIME 宏用于指定哪些属性需要被复制,以及复制的条件。DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly); /* // DOREPLIFETIME_CONDITION用于条件复制,可以根据不同的条件来决定是否复制某个属性。条件包括:// COND_InitialOnly:只发送初始值。// COND_OwnerOnly:只发送给Actor的所有者。// COND_SkipOwner:发送给除所有者之外的所有连接。// COND_SimulatedOnly:只发送给模拟的参与者。// COND_AutonomousOnly:只发送给自治的参与者。// COND_SimulatedOrPhysics:发送给模拟的或具有物理特性的角色。// COND_InitialOrOwner:发送初始包或发送给参与者所有者。// COND_Custom:没有特殊条件,但可以通过SetCustomIsActiveOverride切换开关。*/DOREPLIFETIME_CONDITION(ABlasterCharacter, AO_Yaw, COND_SimulatedOnly); // AO_Yaw 的计算只在权威端进行,复制只在模拟端进行DOREPLIFETIME_CONDITION(ABlasterCharacter, StartingAimRotation, COND_SimulatedOnly); // StartingAimRotation 的计算只在权威端进行,复制只在模拟端进行/* (补充) P54 转身(Turning in Place)*/DOREPLIFETIME_CONDITION(ABlasterCharacter, TurningInPlace, COND_SimulatedOnly); // TurningInPlace 的复制只在模拟端进行/* (补充) P54 转身(Turning in Place)*/ }...
-
编译后,先尝试操控服务器上的人物角色进行测试,再操控客户端 1 进行测试,可以看到转身动画都能正确进行网络同步,问题解决。
54.5 Summary
本节课我们成功实现了角色在站立和蹲伏状态下的原地转身功能。首先,我们创建了“TurningInPlace.h
” 头文件,定义了 “ETurningInPlace
” 枚举类型,包含向左转身、向右转身和不转身三种状态。在 C++ 实现方面,我们在 “BlasterCharacter
” 类中,添加了 “TurningInPlace
” 状态变量和 “TurningInPlaceFunc()
” 函数,基于 “AO_Yaw
” 值判断转身方向,大于 90 度时向右转,小于 -90 度时向左转。
在动画蓝图方面,我们为站立和蹲伏状态分别创建了专门的状态机处理转身动画,使用缓存姿势节点优化蓝图结构,避免使用重复的多个节点。根据 “TurningInPlace
” 状态值在 “Idle
” 状态和 “TurnLeft/TurnRight
” 状态之间切换,修复了蹲伏转身动画中右腿悬空的问题,创建了新的动画资源 “CrouchTurn_L
” 和 “CrouchTurn_R
”。
最后,笔者补充了教学视频没有提到的转身动画网络同步内容,确保服务器及所有客户端都能正确显示转身状态。