《UE5_C++多人TPS完整教程》学习笔记41 ——《P42 蹲伏(Crouching)》
本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P42 蹲伏(Crouching)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
文章目录
- P42 蹲伏(Crouching)
- 42.1 创建蹲伏动作事件
- 42.2 创建蹲伏动画蓝图
- 42.3 加入起立动作事件
- 42.4 Summary
P42 蹲伏(Crouching)
本节课我们将在装备武器时更改动画姿势,我们将学习动画蓝图在多人游戏中的运用。
42.1 创建蹲伏动作事件
-
在虚幻引擎打开 “项目设置”(Project Settings),在 “引擎”(Engine)下找到 “输入”(Input),添加 “动作映射”(Action Mappings)“
Crouch
”,当我们按下键盘 “左 Shift”(Left Shift) 或 “右 Shift”(Right Shift)键能使得人物角色进行蹲伏。
-
在 “
BlasterCharacter.h
” 中声明动作映射 “Crouch
” 的回调函数 “CrouchButtonPressed()
”,接着在 “BlasterCharacter.cpp
” 的 “SetupPlayerInputComponent()
” 函数中绑定回调函数 “CrouchButtonPressed()
”,并借助父类 “ACharacter
” 的内置继承函数 “Crouch
” 完成它的定义,通过查看 “Crouch()
” 的定义以及声明,可以找到一个用来记录 Character 是否在蹲伏的布尔变量 “bIsCrouched
”,它是一个可复制的变量,并且有对应的 Repnotify 函数 “OnRep_IsCrouched()
”,因此我们不需要担心它在客户端和服务器的网络复制问题,这个变量能确保其他机器能同步获取当前机器上人物角色进行蹲伏的信息(如动画姿势等)。/*** BlasterCharacter.h ***/...UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter {GENERATED_BODY()...protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;// 与轴映射相对应的回调函数void MoveForward(float Value); // 人物角色前进或后退void MoveRight(float Value); // 人物角色左移或右移void Turn(float Value); // 人物角色视角左转或右转void LookUp(float Value); // 人物角色俯视或仰视// 与动作映射相对应的回调函数void EquipButtonPressed(); // 人物角色装备武器/* P42 蹲伏(Crouching)*/void CrouchButtonPressed(); // 人物角色蹲伏/* P42 蹲伏(Crouching)*/...}
/*** BlasterCharacter.cpp ***/...// Called to bind functionality to input void ABlasterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {Super::SetupPlayerInputComponent(PlayerInputComponent);// 绑定动作映射PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);PlayerInputComponent->BindAction("Equip", IE_Pressed, this, &ABlasterCharacter::EquipButtonPressed);/* P42 蹲伏(Crouching)*/PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &ABlasterCharacter::CrouchButtonPressed);/* P42 蹲伏(Crouching)*/// 绑定轴映射PlayerInputComponent->BindAxis("MoveForward", this, &ABlasterCharacter::MoveForward);PlayerInputComponent->BindAxis("MoveRight", this, &ABlasterCharacter::MoveRight);PlayerInputComponent->BindAxis("Turn", this, &ABlasterCharacter::Turn);PlayerInputComponent->BindAxis("LookUp", this, &ABlasterCharacter::LookUp); }.../* P42 蹲伏(Crouching)*/ void ABlasterCharacter::CrouchButtonPressed() {Crouch(); } /*** ACharacter.cpp ***/ //void ACharacter::Crouch(bool bClientSimulation) //{ // if (CharacterMovement) // { // if (CanCrouch()) // { // CharacterMovement->bWantsToCrouch = true; // } //#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // else if (!CharacterMovement->CanEverCrouch()) // { // UE_LOG(LogCharacter, Log, TEXT("%s is trying to crouch, but crouching is disabled on this character! (check CharacterMovement NavAgentSettings)"), *GetName()); // } //#endif // } //}/*** ACharacter.h ***/ /** Set by character movement to specify that this Character is currently crouched. */ // UPROPERTY(BlueprintReadOnly, replicatedUsing = OnRep_IsCrouched, Category = Character) // uint32 bIsCrouched : 1; /* P42 蹲伏(Crouching)*/
-
在人物角色动画实例类头文件 “
BlasterAnimInstance.h
” 中定义一个布尔变量 “bIsCrouched
”,用以记录人物角色是否蹲伏。接着,在 “BlasterAnimInstance.cpp
” 的 “NativeUpdateAnimation()
” 函数中通过直接获取 “BlasterCharacter
” 的 “bIsCrouched
” 变量的值来更新我们在 “BlasterAnimInstance.h
” 中定义的 “bIsCrouched
” 的值。/*** BlasterAnimInstance.h ***/...UCLASS() class BLASTER_API UBlasterAnimInstance : public UAnimInstance {GENERATED_BODY()...private:// UPROPERTY:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-engine-uproperties?application_version=5.4UPROPERTY(BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Character”;// 元数据说明符:由于变量为私有变量,需要设置 AllowPrivateAccess 为 true 才能在蓝图中可读class ABlasterCharacter* BlasterCharacter; // 使用动画实例的角色类 UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;float Speed; // 运动速度UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;bool bIsInAir; // 是否在空中UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;bool bIsAccelerating; // 是否在加速UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;bool bWeaponEquipped; // 是否装备了武器/* P42 蹲伏(Crouching)*/UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 “Movemonet”;bool bIsCrouched; // 是否在蹲伏/* P42 蹲伏(Crouching)*/ };...
/*** BlasterAnimInstance.cpp ***/...void UBlasterAnimInstance::NativeUpdateAnimation(float DeltaTime) // 原生(Native)类更新函数 NativeUpdateAnimation() 覆写,用于在每一帧调用以更新动画 {Super::NativeUpdateAnimation(DeltaTime); // 调用父类 AnimInstance 的 NativeUpdateAnimation() 函数if (BlasterCharacter == nullptr) { // 检查 BlasterCharacter 是否声明BlasterCharacter = Cast<ABlasterCharacter>(TryGetPawnOwner()); // 获取动画蓝图实例所属,并向下强制转换(Cast)为 ABlasterCharacter 类}if (BlasterCharacter == nullptr) return;FVector Velocity = BlasterCharacter->GetVelocity(); // 获取人物角色速度向量Velocity.Z = 0.f; // 不关心 Z 轴速度,设置为 0Speed = Velocity.Size(); // 获取人物角色速度向量的模(大小),少了这一行代码在人物角色从怠速到走路到跑步的动画将无法实现转化bIsInAir = BlasterCharacter->GetCharacterMovement()->IsFalling(); // 调用 GetCharacterMovement()->IsFalling() 函数判断人物角色是否掉落从而判断人物角色是否在空中,// 需要添加头文件 "GameFramework/CharacterMovementComponent.h"bIsAccelerating = BlasterCharacter->GetCharacterMovement()->GetCurrentAcceleration().Size() > 0 ? true : false; // 调用 GetCharacterMovement()->GetCurrentAcceleration() 获取人物角色加速度// 判断加速度是否的维度是否大于 0 ,大于 0 说明某个方向上有加速度bWeaponEquipped = BlasterCharacter->IsWeaponEquipped(); // 调用 IsWeaponEquipped() 函数判断人物角色是否装备了武器/* P42 蹲伏(Crouching)*/bIsCrouched = BlasterCharacter->bIsCrouched; // 访问 BlasterCharacter 的 bIsCrouched 变量值来判断人物角色是否在蹲伏/* P42 蹲伏(Crouching)*/ }...
42.2 创建蹲伏动画蓝图
- 编译后在虚幻引擎中打开动画蓝图 “
BlasterAnimBP
”,然后在 “AnimGraph
” 面板中打开状态机 “Equipped
” 的编辑界面,在右侧内容浏览器中将动画资产 “Crouch_Idle_Rifle_Hip
” 拖拽至面板中,将生成的节点重命名为 “CrouchIdle
”,并与 “Idle
” 节点进行双向连接。
- 双击 “Idle 到 CrouchIdle”(Idle to CrouchIdle) 转换
按钮,在 “Idle 到 CrouchIdle(规则)” 面板中添加 “
Get is Crouched
” 节点,并将该节点的输出引脚和自动生成的 “Result
” 节点的 “Can Enter Transition
” 输入引脚连接,这段蓝图表示如果 “Is Crouched
” 变量值为真则将人物角色的状态由站立转换为蹲伏。同理,双击 “CrouchIdle 到 Idle”(CrouchIdle to Idle) 转换按钮,在 “CrouchIdle 到 Idle(规则)” 面板中添加 “Get is Crouched
” 节点和 “Not
” 节点,并将 “Get is Crouched
” 节点的输出引脚连接 “NOT 布尔
”(NOT Boolean) 节点的输入引脚,然后再将 “NOT 布尔
” 节点的输出引脚与 “Result
” 节点的 “Can Enter Transition
” 引脚连接。
- 编译、保存后进行测试,当我们操控服务器上的人物角色按下 “E” 键拾取并装备武器,然后按下 “左 Shift” 键时,人物角色并未由站立的动画姿势转换为蹲伏的动画姿势。
- 这是因为我们在人物角色蓝图 “
BP_BlasterCharacter
” 中并未勾选(Uncheck) “角色移动(CharMoveComp)” 组件 “移动能力” 下的 “可蹲伏”(Can Crouch)属性,把该属性选上即可解决问题。
当然我们也可以使用 C++ 解决。在 Visual Studio 中通过查看 “Crouch()
” 的定义以及声明,可以找到 “ACharacter
” 下的函数 “CanCrouch()
”,它决定是否赋予人物角色进行蹲伏的能力,并且由它的定义我们可以找到移动组件 “CharacterMovement
” 下的一个内联函数 “CanEverCrouch()
”,在它的定义中返回值是一个名为 “bCanCrouch
” 的布尔变量,它的布尔值决定了是否允许人物角色进行蹲伏,于是我们可以在 “BlasterCharacter.h
” 的构造函数 “ABlasterCharacter()
” 中通过访问移动组件 “CharacterMovement
” 来将 “bCanCrouch
” 的值修改为真。/*** BlasterCharacter.cpp ***/...// Sets default values ABlasterCharacter::ABlasterCharacter() {...Combat = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent")); // 基于枪战功能组件类创建对象Combat->SetIsReplicated(true); // 指定为复制组件,这里我们并不需要像上节课一样注册枪战功能组件并重写 GetLifetimeReplicatedProps() 函数/* P42 蹲伏(Crouching)*/GetCharacterMovement()->NavAgentProps.bCanCrouch = true; // 赋予人物角色可进行蹲伏的能力/* P42 蹲伏(Crouching)*/ }...
42.3 加入起立动作事件
-
修改 “
BlasterCharacter.cpp
” 中蹲伏动作映射的回调函数 “CrouchButtonPressed()
” 的定义,使得当按下 “左 Shift” 或 “右 Shift” 键时,如果人物角色在蹲伏时,则调用函数 “Uncrouch
” 使得人物角色起立,从蹲伏变为站立;如果人物角色在站立,则改为蹲伏。/*** BlasterCharacter.cpp ***/.../* P42 蹲伏(Crouching)*/ void ABlasterCharacter::CrouchButtonPressed() {if (bIsCrouched) {UnCrouch(); // 起立}else {Crouch(); // 蹲伏} } /*** ACharacter.cpp ***/ //void ACharacter::Crouch(bool bClientSimulation) //{ // if (CharacterMovement) // { // if (CanCrouch()) // { // CharacterMovement->bWantsToCrouch = true; // } //#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // else if (!CharacterMovement->CanEverCrouch()) // { // UE_LOG(LogCharacter, Log, TEXT("%s is trying to crouch, but crouching is disabled on this character! (check CharacterMovement NavAgentSettings)"), *GetName()); // } //#endif // } //}/*** ACharacter.h ***/ /** Set by character movement to specify that this Character is currently crouched. */ // UPROPERTY(BlueprintReadOnly, replicatedUsing = OnRep_IsCrouched, Category = Character) // uint32 bIsCrouched : 1; /* P42 蹲伏(Crouching)*/
-
编译后进行测试,当按下 “左 Shift” 或 “右 Shift” 键时,如果本地的人物角色(无论是客户端还是服务器)在蹲伏时,则人物将从蹲伏变为站立;如果人物角色在站立,则变为蹲伏。
并且可以发现本地的人物角色(无论是客户端还是服务器)在蹲伏然后起立后再按下空格键进行跳跃,其他机器上也能看到本地的人物角色在跳跃(但如果人物角色在蹲伏,按下空格键并不会进行跳跃),说明很多必要的属性包括跳跃在底层(Behind the scenes)是被处理并复制了的。
-
当然我们的动画蓝图还没有彻底完善充实(Is not quite fleshed out yet):当人物角色蹲伏时,按下 “
W
”、“S
”、“A
” 或 “D
” 键时,可以看到人物角色不是蹲着行走的;人物角色在蹲伏然后起立后,按下 “W
”、“S
”、“A
” 或 “D
” 键,可以看到人物角色无法再进行跑动。我们将在后续合适的时间里(In due time)解决上述问题。 -
按下 “
~
” 键打开控制台,输入 “show Collision
” 命令行,可以在场景中看到人物角色的胶囊体组件,当人物角色蹲伏时,胶囊体组件会变小,也就是说 “Crunch()
” 函数会自动帮我们调整胶囊体的大小,这样我们就可以在蹲伏时到达站立时难以通过的低处,不仅如此,我们在蹲伏时移动速度也变慢了。
-
胶囊体的大小和蹲伏时的移动速度都可以在人物角色蓝图 “
BP_BlasterCharacter
” 的 “角色移动(CharMoveComp)” 组件中修改,在 “细节” 面板中可以找到 “角色移动(通用设置)”(CHARACTER MOVEMENT(GENERAL SETTINGS))下的 “蹲伏半高”(Crouched Half Height)和 “角色移动:行走”(CHARACTER MOVEMENT: WALKING) “最大蹲伏行走速度” 属性,将这两个属性的值修改即可(教学视频中 “蹲伏半高” 从 40.0 cm 改为 60.0 cm,“最大蹲伏行走速度” 从 300.0 cm/s 改为 100.0 cm/s)。
-
总的来说,当引擎中有类似于蹲伏这样的内置函数时,学习并利用它比我们自己重新造轮子(Reinventing wheels)更好,因为很多通常我们在多人游戏中无法考虑到的细节,比如变量复制,都会被引擎开发者考虑在内(Take multiplayer into account)。
42.4 Summary
本节课我们学习了虚幻引擎内置与多人游戏相关的蹲伏函数并利用它实现了人物角色的蹲伏功能。首先,在项目设置中添加了"Crouch
" 动作映射绑定左右 Shift 键,在角色类 “ABlasterCharacter
” 中声明并实现了蹲伏回调函数,利用父类 “ACharacter
” 内置的 “Crouch()
” 函数处理网络复制问题,确保蹲伏状态能同步到所有客户端。动画实例类新增 “bIsCrouched
” 变量,通过直接访问角色类 “ACharacter
” 的复制变量 “bIsCrouched
” 的布尔值实现实时更新。
接着,在动画蓝图 “BlasterAnimBP
” 中为 “Equip
” 状态机添加蹲伏待机动画节点 “CrouchIdle
”,通过转换规则实现站立待机 “Idle
” 与蹲伏待机 “CrouchIdle
” 姿势的自动切换。进行测试后发现人物角色无法蹲伏,这是因为我们在人物角色蓝图 “BP_BlasterCharacter
” 中未启用 “角色移动(CharMoveComp)” 组件中人物角色可蹲伏能力,同时我们也可以在 C++ 代码中实现,即通过访问人物角色的移动组件 “CharacterMovement
” 来将 “bCanCrouch
” 的值修改为真,从而赋予角色蹲伏权限。
随后,借助虚幻引擎内置的 “Uncrouch()
” 函数加入站立事件使得按下 Shift 键时人物角色能在蹲伏/站立状态间切换,测试结果表明蹲伏/站立状态间切换得动画过渡流畅且网络同步正常。
最后,我们在控制台输入命令行 “show Collision
” 监测场景中的碰撞情况,可以观察到人物角色蹲伏时胶囊体碰撞体积自动调整,移动速度降低的特性,这些属性参数都可在人物角色蓝图 “BP_BlasterCharacter
” 的 “角色移动(CharMoveComp)” 组件中进行设置。
整个实现过程充分证明:在开发多人游戏时,利用虚幻引擎引擎内置功能函数比重复造轮子更高效可靠。