《UE5_C++多人TPS完整教程》学习笔记54 ——《P55 旋转根骨骼(Rotate Root Bone)》
本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P55 转身(Turning in Place)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
文章目录
- P55 旋转根骨骼(Rotate Root Bone)
- 55.1 C++ 实现转身插值
- 55.2 在蓝图中旋转根骨骼
- 55.3 Summary
P55 旋转根骨骼(Rotate Root Bone)
本节课我们将在人物角色动画蓝图中加入蓝图节点 “旋转根骨骼”(Rotate Root Bone),使得我们的人物角色在进行转身时能旋转到位。
55.1 C++ 实现转身插值
- 我们想要人物角色在转身时,其根骨骼在转到 90° 之前保持静止,到达 90 ° 之后再将根骨骼向左或向右旋转。
- 在 “
BlasterCharacter.h
” 中声明可复制变量 “InterpAO_Yaw
”,它将作为对 “AO_Yaw
” 进行插值的中间变量。/*** BlasterCharacter.h ***/...UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...private:...UPROPERTY(Replicated) // 可复制变量float AO_Yaw; // 人物角色瞄准偏移偏航角/*P55 旋转根骨骼(Rotate Root Bone)*/UPROPERTY(Replicated) // 可复制变量float InterpAO_Yaw; // 用于对 AO_Yaw 插值/*P55 旋转根骨骼(Rotate Root Bone)*/float AO_Pitch; // 人物角色瞄准偏移俯仰角UPROPERTY(Replicated) // 可复制变量FRotator StartingAimRotation; // 人物角色的起始瞄准旋转UPROPERTY(Replicated) // 可复制变量ETurningInPlace TurningInPlace; // 人物角色的原地转身状态 void TurningInPlaceFunc(float DeltaTime); // 实现人物角色原地转身...}
- 在 “
BlasterCharacter.cpp
” 的函数 “GetLifetimeReplicatedProps()
” 中为 “InterpAO_Yaw
” 注册生命周期,指明其复制条件为只在模拟端复制。接着,在瞄准偏移函数 “AimOffset()
” 中修改代码,当人物角色站立时,启用控制器旋转控制偏航,人物角色正前方方向将随着摄像机转动改变;若未进行转身则令 “InterpAO_Yaw
” 始终和 “AO_Yaw
” 保持一致。随后,在转身函数 “TurningInPlaceFunc()
” 中添加代码,如果人物角色要进行转身,则使用函数 “FInterpTo()
” 对 “AO_Yaw
” 进行插值并进行更新,当 “AO_Yaw
” 小于一定值时,停止转身,重置人物角色起始瞄准旋转 “StartingAimRotaiton
”。/*** BlasterCharacter.cpp ***/...// 重写复制属性函数 void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {// 调用SuperSuper::GetLifetimeReplicatedProps(OutLifetimeProps);...DOREPLIFETIME_CONDITION(ABlasterCharacter, AO_Yaw, COND_SimulatedOnly); // AO_Yaw 的计算只在权威端或本地控制端进行,复制只在模拟端进行/*P55 旋转根骨骼(Rotate Root Bone)*/DOREPLIFETIME_CONDITION(ABlasterCharacter, InterpAO_Yaw, COND_SimulatedOnly); // InterpAO_Yaw 的计算只在权威端或本地控制端进行,复制只在模拟端进行/*P55 旋转根骨骼(Rotate Root Bone)*/DOREPLIFETIME_CONDITION(ABlasterCharacter, StartingAimRotation, COND_SimulatedOnly); // StartingAimRotation 的计算只在权威端或本地控制端进行,复制只在模拟端进行DOREPLIFETIME_CONDITION(ABlasterCharacter, TurningInPlace, COND_SimulatedOnly); // TurningInPlace 的复制只在模拟端进行 }...// 瞄准偏移 void ABlasterCharacter::AimOffset(float DeltaTime) {if (Combat && Combat->EquippedWeapon == nullptr) return;FVector Velocity = GetVelocity(); // 获取人物角色速度向量Velocity.Z = 0.f; // 不关心 Z 轴速度,设置为 0float Speed = Velocity.Size(); // 获取人物角色速度向量的模(大小)bool bIsInAir = GetCharacterMovement()->IsFalling(); // 判断人物角色是否掉落从而判断人物角色是否在空中if (HasAuthority() || IsLocallyControlled()) // 只在权威端或本地控制端计算 AO_Yaw{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; // 获取人物角色瞄准偏航角/*P55 旋转根骨骼(Rotate Root Bone)*/// bUseControllerRotationYaw = false; // 禁用控制器旋转偏航if (TurningInPlace == ETurningInPlace::ETIP_NotTurning) { // 如果不转身InterpAO_Yaw = AO_Yaw; // InterpAO_Yaw 始终和 AO_Yaw 保持一致}bUseControllerRotationYaw = true; // 启用用控制器旋转偏航,人物角色正前方方向将随着摄像机转动改变/*P55 旋转根骨骼(Rotate Root Bone)*/TurningInPlaceFunc(DeltaTime);}}...}...// 实现人物角色原地转身 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; // 向左转身}/*P55 旋转根骨骼(Rotate Root Bone)*/if (TurningInPlace != ETurningInPlace::ETIP_NotTurning) { // 如果开始向左或向右转身InterpAO_Yaw = FMath::FInterpTo(InterpAO_Yaw, 0.f, DeltaTime, 4.f); // 由于未开始转身前 InterpAO_Yaw 与 AO_Yaw 相等,因此开始转身时这里相对于从 0.0 到 AO_Yaw 插值AO_Yaw = InterpAO_Yaw; // 将每一帧的插值赋给 AO_Yaw,以便在蓝图中获取 AO_Yaw,它的相反数将作为旋转根骨骼节点的入参if (FMath::Abs(AO_Yaw) < 15.f) { // 当 AO_Yaw 小于一定值,这里设为 15.0TurningInPlace = ETurningInPlace::ETIP_NotTurning; // 停止转身StartingAimRotation = FRotator(0.f, GetBaseAimRotation().Yaw, 0.f); // 重置起始瞄准旋转}}/*P55 旋转根骨骼(Rotate Root Bone)*/ }
55.2 在蓝图中旋转根骨骼
-
编译后,打开虚幻引擎,在 “
BlasterAnimBP
” 的 “AnimGraph
” 事件面板中添加蓝图节点 “旋转根骨骼”(Rotate Root Bone),绘制下图所示的蓝图。
-
人物角色进行转身之后还无法回到待机状态,因此创建 “TurnLeft到Idle” 和 “TurnRight到Idle” 的状态规则转换,并为其绘制蓝图,当人物角色 移动速度大于 0(
Speed > 0
)或转身动画 “剩余时间(比率)”(Time Remaining (ratio) < 0.1
) 小于 0.1 到或在空中(bIsInAir == true
)或未进行转身时,由转身状态过渡到待机状态。
-
为了让转身更自然一些,可以设置 “
TurnLeft
” 和 “TurnRight
” 动画播放速率为 1.5。
-
仿照上述步骤,构建蹲伏转身到蹲伏待机的过度规则蓝图。
-
编译、保存后进行多人游戏测试,先操控服务器上的人物角色进行转身和蹲伏转身,再操控其中客户端,可以发现所有机器上转身和蹲伏转身的动画都能非常自然流畅,且都能正确地进行网络同步。
注意:
如果没有对变量 “InterpAO_Yaw
” 进行网络同步,在多人游戏测试时人物角色会出现身体抖动的情况。
55.3 Summary
本节课我们成功实现了角色转身时的根骨骼旋转功能,使转身动画更加自然流畅。首先在 C++ 层面,我们添加了 “InterpAO_Yaw
” 变量用于对 “AO_Yaw
” 进行平滑插值处理,并对 “InterpAO_Yaw
” 进行网络同步,保证后续的转身动画不会出现抖动。接着,在“TurningInPlaceFunc()
” 中使用 “FInterpTo()
” 函数实现 “AO_Yaw
” 插值,保证转身角度可以平滑地过渡。在动画蓝图方面,添加了 “旋转根骨骼” 节点,使用 “InterpAO_Yaw
” 值控制根骨骼旋转,基于人物角色移动速度、转身动画剩余时间和空中状态的进行判断,构建转身(蹲伏转身)到待机(蹲伏待机)的状态转换规则。随后,调整转身动画的播放速率使转身更加自然。测试结果表明,人物角色转身时身体能够平滑转向,且在多人游戏环境下服务器和所有客户端上都能正确同步显示转身动画。