《UE5_C++多人TPS完整教程》学习笔记47 ——《P48 瞄准行走(Aim Walking)》


本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 ------ 《P48 瞄准行走(Aim Walking)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author)Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么


文章目录

  • [P48 瞄准行走(Aim Walking)](#P48 瞄准行走(Aim Walking))
  • [48.1 创建瞄准行走混合空间蓝图类](#48.1 创建瞄准行走混合空间蓝图类)
  • [48.2 修改和同步瞄准行走速度](#48.2 修改和同步瞄准行走速度)
  • [48.3 添加蹲伏瞄准行走状态及瞄准待机动画](#48.3 添加蹲伏瞄准行走状态及瞄准待机动画)
  • [48.4 解决摄像机与与人物角色的碰撞问题](#48.4 解决摄像机与与人物角色的碰撞问题)
  • [48.5 Summary](#48.5 Summary)

P48 瞄准行走(Aim Walking)

本节课我们将创建与瞄准相关的动画混合空间,并且实现瞄准时的行走、蹲伏、奔跑状态。


48.1 创建瞄准行走混合空间蓝图类

  1. 打开虚幻引擎,在 "/Game/Contents/Blueprints/Character/Animation" 目录下以 "SK_EpicCharacter_Skeleton" 为骨骼创建混合空间1D(Blend Space 1D)蓝图类,命名为 "AimWalk"。

    打开 "AimWalk" 混合空间编辑器,在左侧 "资产详情 "(Asset Details)面板的 "Axis Settings" 选项卡下展开 "水平坐标 "(Horizontal Axis)设置 "名称 "(Name)为 "YawOffset ",并设置 "最小轴值 "(Minimum Axis Value)为 -180.0 ,"最大轴值 "(Maximum Axis Value)为 180.0

    在右侧资产浏览器中拖拽动画资产 "Walk_Fwd_Rifle_Ironsights" 的同时按下键盘 Shift 键,将其放置在资产编辑器坐标轴 (0.0, 0.0) 处生成采样点。同理,分别将向左蹲伏行走的动画资产 "Walk_Lt_Rifle_Ironsights" 和向右蹲伏行走 "Walk_Rt_Rifle_Ironsights" 放置在 (-90.0, 0.0)(90.0, 0.0) 处,然后将向后蹲伏行走的动画资产 "Walk_Bwd_Rifle_Ironsights" 放置在 (-180.0, 0.0)(180.0, 0.0) 处。

  2. 创建蹲伏瞄准行走的混合空间1D蓝图类,命名为 "CrouchAimWalk"。打开 "CrouchAimWalk" 混合空间编辑器,在左侧资产详情面板的 "Axis Settings" 选项卡下展开 "水平坐标" 设置 "名称" 为 "YawOffset",并设置 "最小轴值" 为 -180.0 ,"最大轴值" 为 180.0

    在右侧资产浏览器中拖拽动画资产 "Crouch_Walk_Fwd_Rifle_Ironsights" 的同时按下键盘 Shift 键,将其放置在资产编辑器坐标轴 (0.0, 0.0) 处生成采样点。同理,分别将向左蹲伏行走的动画资产 "Crouch_Walk_Lt_Rifle_Ironsights" 和向右蹲伏行走 "Crouch_Walk_Rt_Rifle_Ironsights" 放置在 (-90.0, 0.0)(90.0, 0.0) 处,然后将向后蹲伏行走的动画资产 "Crouch_Walk_Bwd_Rifle_Ironsights" 放置在 (-180.0, 0.0)(180.0, 0.0) 处。

  3. 在虚幻引擎中打开动画蓝图 "BlasterAnimBP",进入 "Standing" 状态机编辑界面,在从状态节点 "Run" 中引出一条线,连接新的状态节点 "AimWalking",接着再从 "AimWalking" 引回一条线连接 "Run"。随后,从 "AimWalking" 引出一条线连接 "JumpStart"。

  4. 双击进入 "AimWalking" 状态编辑界面,在右下角资产浏览器中拖拽混合空间动画蓝图类 "AimWalk" 至面板中生成蓝图节点,将该节点的 "Animation Pose" 输出引脚与 "输出动画姿势 "(Out Animation Pose)节点的 "Result" 引脚连接。添加 "获取 YawOffset "(Get YawOffset)节点,将该节点的输出引脚与 "AimWalk" 节点的 "YawOffset" 输入引脚连接。

  5. 进入 "Run 到 AimWalking " 转换规则蓝图编辑界面,在面板中添加蓝图节点 "获取 Aiming "(Get Aiming),按下图所示连线绘制蓝图,当人物角色开始瞄准(bAiming == true)时,就从 "Run" 状态转换到 "AimWalking" 状态。

  6. 进入 "AimWalking 到 Run " 转换规则蓝图编辑界面,在面板中添加蓝图节点 "获取 Aiming "(Get Aiming)、"NOT布尔 "(NOT Boolean)、"获取 Is Accelarating "(Get Is Accelarating)以及 "OR布尔 "(OR Boolean),按下图所示连线绘制蓝图,当人物角色不在瞄准(bAiming == false)或不在加速(bIsAccelarating == true)时,就从 "AimWalking" 状态转换到 "Run" 状态。

  7. 进入 "AimWalking 到 JumpStart " 转换规则蓝图编辑界面,在面板中添加蓝图节点 "获取 Is In Air "(Get Is In Air),按下图所示连线绘制蓝图,当人物角色在空中时就从 "AimWalking" 状态转换到 "JumpStart"。

  8. 编译、保存后进行测试,在人物角色装备武器后,长按鼠标右键并按下方向键,人物角色将开始行走瞄准,该状态下按下空格键,人物角色将进入跳跃状态,然后重新回到待机状态。


48.2 修改和同步瞄准行走速度

  1. 我们需要在瞄准行走时更改我们的最大行走速度,因为从奔跑状态转换到瞄准行走状态时仍以相同的速度在移动,因此需要降低行走速度。

  2. 在 Visual Studio 中打开 "CombatComponent.h",声明人物角色的基础行走速度 "BaseWalkSpeed" 和瞄准行走速度 "AimWalkSpeed"。接着,打开 "CombatComponent.cpp",在构造函数 "UCombatComponent()" 将它们初始化。随后,在 "BeginPlay()" 设置人物角色移动组件的最大行走速度 "MaxWalkSpeed" 为 "BaseWalkSpeed",并在 "SetAiming()" 中根据 "bIsAiming" 的值设置 "MaxWalkSpeed" 为 "AimWalkSpeed" 或 "BaseWalkSpeed"。

    cpp 复制代码
    /*** CombatComponent.h ***/
    
    ...
    
    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class BLASTER_API UCombatComponent : public UActorComponent
    {
        GENERATED_BODY()
    
        ...
    
    private:
        class ABlasterCharacter* Character;	// 声明人物角色类,避免反复 casting 到 ABlasterCharacter
    
        // UPROPERTY(Replicated)
        // class AWeapon* EquippedWeapon;	// 保存当前装备的武器
        UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
        class AWeapon* EquippedWeapon;		// 保存当前装备的武器
    
        UPROPERTY(Replicated)				// 可复制变量
        bool bAiming;						// 是否在瞄准
    
        /* P48 瞄准行走(Aim Walking)*/
        UPROPERTY(EditAnywhere)
        float BaseWalkSpeed;				// 基础行走速度
        
        UPROPERTY(EditAnywhere)
        float AimWalkSpeed;					// 瞄准行走速度
        /* P48 瞄准行走(Aim Walking)*/
    };
    cpp 复制代码
    /*** CombatComponent.cpp ***/
    
    ...
    
    // Sets default values for this component's properties
    UCombatComponent::UCombatComponent()
    {
        // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
        // off to improve performance if you don't need them.
        PrimaryComponentTick.bCanEverTick = false;
    
        /* P48 瞄准行走(Aim Walking)*/
        BaseWalkSpeed = 600.f;  // 初始化基础行走速度
        AimWalkSpeed = 450.f;   // 初始化瞄准行走速度
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    // Called when the game starts
    void UCombatComponent::BeginPlay()
    {
        Super::BeginPlay();
        
        /* P48 瞄准行走(Aim Walking)*/
        if (Character) {
            Character->GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed;    // 游戏开始设置人物角色最大行走速度为基础行走速度
        }
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    ...
    
    // 设置 bAiming
    void UCombatComponent::SetAiming(bool bIsAiming)
    {
        bAiming = bIsAiming;
    
        // 由虚幻引擎官方文档 "从服务器调用的 RPC" 的表格 "Server" 这一列和 "从客户端调用的 RPC" 的表格第一行 "Server" 列可知,
        // 以 Server 关键字声明的 RPC 函数无论在客户端还是服务器上调用都是在服务器上执行,因此无需对当前机器是客户端还是服务器端进行判断。
        //if (!Character->HasAuthority()) {
        //	ServerSetAiming(bIsAiming);
        //}
        ServerSetAiming(bIsAiming);
    
        /* P48 瞄准行走(Aim Walking)*/
        if (Character) {
            Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed; // 如果人物角色正在瞄准则设置最大行走速度为瞄准行走速度,否则为基础行走速度
        }
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    ...
  3. 编译后打开虚幻引擎进行测试,当我们从奔跑到瞄准行走时,很难观察到人物角色的最大行走速度是否发生了变化。退出测试,打开人物角色蓝图类 "BP_EpicCharacter",在左侧组件面板选中 "Combat(Combat Component)",然后在右侧细节面板中将 "COMBAT COMPONENT" 下的 "Aim Walk Speed" 改为 50.0 。再次进行测试,如果我们控制视口中服务器上的人物角色进行瞄准行走,可以看到被控人物角色在服务器和所有客户端上的移动速度很慢,符合刚刚修改过的数值 50.0 ;但是如果我们控制客户端上的人物角色进行瞄准行走,人物角色的移动速度在服务器和所有客户端上的移动速度都很快,明显不符合 50.0 ,而更像在 C++ 代码中设置的 "450.0 ",这说明我们在 "BP_EpicCharacter" 中修改的 "Aim Walk Speed" 在客户端上没有与服务器进行同步。

  4. 解决上述问题的方法就是在 "CombatComponent.cpp" 的 RPC 实施函数 "ServerSetAiming_Implementation()" 中根据 "bIsAiming" 的值设置 "MaxWalkSpeed" 为 "AimWalkSpeed" 或 "BaseWalkSpeed"。

    cpp 复制代码
    /*** CombatComponent.cpp ***/
    
    ...
    
    
    // 设置 bAiming
    void UCombatComponent::SetAiming(bool bIsAiming)
    {
        bAiming = bIsAiming;
    
        // 由虚幻引擎官方文档 "从服务器调用的 RPC" 的表格 "Server" 这一列和 "从客户端调用的 RPC" 的表格第一行 "Server" 列可知,
        // 以 Server 关键字声明的 RPC 函数无论在客户端还是服务器上调用都是在服务器上执行,因此无需对当前机器是客户端还是服务器端进行判断。
        //if (!Character->HasAuthority()) {
        //	ServerSetAiming(bIsAiming);
        //}
        ServerSetAiming(bIsAiming);
    
        /* P48 瞄准行走(Aim Walking)*/
        if (Character) {
            Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed; // 如果人物角色正在瞄准则设置最大行走速度为瞄准行走速度,否则为基础行走速度
        }
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    // 服务器可靠 RPC 函数,bAiming 会被服务器复制到所有客户端
    void UCombatComponent::ServerSetAiming_Implementation(bool bIsAiming)
    {
        bAiming = bIsAiming;	// 由于函数在服务器上执行且 bAiming 可复制,服务器会将 bAiming 值的更新复制到所有客户端
    
        /* P48 瞄准行走(Aim Walking)*/
        if (Character) {
            Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed;	// 如果人物角色正在瞄准则设置最大行走速度为瞄准行走速度,否则为基础行走速度
        }
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    ...
  5. 编译后进行测试,可以观察到我们在 "BP_EpicCharacter" 中修改的 "Aim Walk Speed" 在客户端上会与服务器进行同步,无论控制服务器还是客户端的人物角色,它的瞄准行走速度都会很慢(Slowly inching along)。

  6. 回到 "BP_EpicCharacter" 中修改 "Aim Walk Speed" 为 400.0 ,这个速度对于我们人物角色的瞄准行走动画来说相对比较自然。


48.3 添加蹲伏瞄准行走状态及瞄准待机动画

  1. 我们还需要考虑到蹲伏瞄准行走状态。在 "Crouching" 状态机编辑界面中,添加状态节点 "CrouchAimWalking",并将该节点与 "CrouchWalking" 进行双向连接以生成两者之间的转换规则。仿照 48.1 步骤 4------6,完善 "CrouchAimWalking" 状态、"CrouchWalking 到 CrouchAimWalking " 转换规则以及 "CrouchAimWalking 到 CrouchWalking " 转换规则的蓝图。



  2. 编译、保存后进行测试,可以观察到我们的人物角色可以一边蹲伏一边进行瞄准,我们并不需要改变人物角色蹲伏瞄准行走的最大速度,即便它看起来速度有些慢,有点偷偷摸摸的感觉(Kind of sneaking)。

  3. 在之前的测试中,可以很容易发现人物角色只有在进行了移动才播放与瞄准相关的动画姿势,缺少待机状态下的瞄准动画姿势,因此需要进一步完善。在状态机 "Crouching" 中打开 "CrouchingIdle" 状态节点编辑界面,在右下角资产浏览器中拖拽动画资产 "Crouch_Idle_Rifle_Ironsights" 至面板中生成蓝图节点,新增蓝图节点 "按布尔混合姿势 "(Blend Poses by bool)以及 "获取 Aiming",并将生成的蓝图变量 "Aiming" 连接到 "按布尔混合姿势 " 节点的 "Active Value" 引脚;接着将的 "Crouch_Idle_Rifle_Ironsights" 节点的输出引脚连接到 "按布尔混合姿势 " 节点的 "真 姿势 "(True Pose)引脚,将原先的 "Crouch_Idle_Rifle_Hips" 节点的输出引脚连接到 "按布尔混合姿势 " 节点的 "False 姿势 "(False Pose)引脚,最后将 "按布尔混合姿势 " 的输出引脚连接到 "OutputPose" 节点的 "Result" 输入引脚。

    这段蓝图表示通过 "Aiming" 的布尔值来判断输出的蹲伏待机动画姿势,如果 "Aiming" 为真,则最终的输出姿势为 "Crouch_Idle_Rifle_Ironsights" 状态机中的动画姿势,即人物角色蹲伏瞄准待机的动画姿势,如果 "Aiming" 为假,则最终的输出姿势为 "Crouch_Idle_Rifle_Hips" 状态机中的动画姿势,即人物角色蹲伏待机的动画姿势。

  4. 同理,修改状态机 "Standing" 中 状态节点 "CrouchingIdle" 的蓝图。

  5. 编译编译、保存后进行测试,可以观察到我们的人物角色在待机时,如果按下鼠标右键进行瞄准,人物角色瞄准站立或蹲伏瞄准待机的动画姿势将会被播放,反之站立或蹲伏待机的动画姿势会被播放。

  6. 补充 )在测试中我们还会发现,人物角色在奔跑或蹲伏移动时进行瞄准,此时我们不再进行任何移动,但仍进行瞄准,人物角色播放的不是瞄准待机的动画姿势,而是瞄准行走的动画姿势。

    解决方法是在 "Run 到 AimWalking " 和 "CrouchWalking 到 CrouchAimWalking " 转换规则蓝图中增加过渡转换的条件 "AND bIsAccelarating"。


48.4 解决摄像机与与人物角色的碰撞问题

  1. 当我们操控视口的人物角色进行移动时,如果走到一个客户端(下图右下窗口)人物角色的摄像机前面,摄像机会突然放大,这是摄像机为了避开视口中的人物角色而不断拉近导致的。

  2. 为解决上述问题,我们需要设置摄像机的碰撞预设(Collision Presets)。在 Visual Studio 中打开 "BlasterCharacter.cpp",添加头文件 "Components/CapsuleComponent.h",在函数 "ABlasterCharacter()" 中添加代码,使得人物角色的胶囊体组件和骨骼网格体组件忽略摄像机碰撞通道的所有检测,即当摄像机的射线检测打到这个胶囊体或骨骼网格体时,会直接穿过去,就像它不存在一样,不会报告任何碰撞命中信息。

    cpp 复制代码
    ...
    
    /* P48 瞄准行走(Aim Walking)*/
    # include "Components/CapsuleComponent.h"
    /* P48 瞄准行走(Aim Walking)*/
    
    // Sets default values
    ABlasterCharacter::ABlasterCharacter()
    {
        ...
    
        Combat = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));			// 基于枪战功能组件类创建对象
        Combat->SetIsReplicated(true);														// 指定为复制组件,这里我们并不需要像上节课一样注册枪战功能组件并重写 GetLifetimeReplicatedProps() 函数
    
        GetCharacterMovement()->NavAgentProps.bCanCrouch = true;							// 赋予人物角色可进行蹲伏的能力
    
        /* P48 瞄准行走(Aim Walking)*/
        GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);	// 让人物角色的胶囊体组件忽略来自摄像机碰撞通道的所有检测
        GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);				// 让人物角色的骨骼网格体组件忽略来自摄像机碰撞通道的所有检测
        /* P48 瞄准行走(Aim Walking)*/
    }
    
    ...
  3. 编译后进行测试,问题解决。


48.5 Summary

本节课我们实现了人物角色在瞄准状态下的行走动画,并解决了相关的网络同步和摄像机碰撞问题。

首先,我们创建了站立瞄准行走混合空间1D "AimWalk" 和蹲伏瞄准行走混合空间1D "CrouchAimWalk",为它们的水平轴 "YawOffset" 配置了范围(-180 到 180),并将各个方向的瞄准行走动画精确放置在坐标轴的关键点上。

接着,我们在 "Standing" 和 "Crouching" 状态机中分别添加了 "AimWalking" 和 "CrouchAimWalking" 状态,并设置了与 "Run/CrouchWalking" 状态之间的转换规则。转换条件基于 "bAiming"(是否瞄准)和 "bIsAccelerating"(是否加速)变量,确保角色在瞄准且移动时进入瞄准行走状态。

然后,我们解决了瞄准行走速度的网络同步问题。在战斗组件 "UCombatComponent" 中添加了 "BaseWalkSpeed" 和 "AimWalkSpeed" 变量,通过在 "SetAiming()" 和 "ServerSetAiming_Implementation()" 中设置角色移动速度,确保了服务器与所有客户端上瞄准行走速度的正确同步。

此外,我们使用 "按布尔混合姿势" 蓝图节点,在 "Idle" 和 "CrouchIdle" 状态中根据 "bAiming" 值混合待机和瞄准待机动画,完善了角色在静止瞄准状态下的表现。

最后,我们解决了摄像机与人物角色的碰撞问题,通过设置胶囊体组件和骨骼网格体组件忽略摄像机碰撞通道 "ECC_Camera",防止摄像机在靠近其他角色时异常拉近。

最终实现的系统使得角色能够在站立和蹲伏状态下流畅地进行瞄准行走,且在多人游戏环境下所有客户端都能正确同步显示,大大增强了游戏的视觉表现力和操作体验。

48.3 添加蹲伏瞄准行走状态及瞄准待机动画步骤 6 中,人物角色在奔跑或蹲伏移动时进行瞄准,如果不再进行任何移动,人物角色播放的不是瞄准待机的动画姿势,而是瞄准行走的动画姿势,这需要在 "Run 到 AimWalking " 和 "CrouchWalking 到 CrouchAimWalking " 转换规则蓝图中增加过渡转换的条件 "AND bIsAccelarating" 以进行修复。


相关推荐
是店小二呀6 小时前
【ProtoBuf 】C++ 网络通讯录开发实战:ProtoBuf 协议设计与 HTTP 服务实现
网络·c++·http·protobuf
long_run6 小时前
C++之箭头操作符
c++
啊?啊?6 小时前
13 选 list 还是 vector?C++ STL list 扩容 / 迭代器失效问题 + 模拟实现,对比后再做选择
c++·stl容器·模拟实现
MSTcheng.7 小时前
【C++】C++入门—(中)
开发语言·c++
wheeldown10 小时前
【Linux】为什么死循环卡不死 Linux?3 个核心逻辑看懂进程优先级与 CPU 调度密码
linux·运维·服务器·开发语言·c++·unix·进程
Want59510 小时前
C/C++哆啦A梦
c语言·开发语言·c++
津津有味道13 小时前
15693协议ICODE SLI 系列标签应用场景说明及读、写、密钥认证操作Qt c++源码,支持统信、麒麟等国产Linux系统
linux·c++·qt·icode·sli·15693
一枝小雨14 小时前
【C++】编写通用模板代码的重要技巧:T()
开发语言·c++·笔记·学习笔记
蒹葭玉树16 小时前
【C++上岸】C++常见面试题目--数据结构篇(第十七期)
数据结构·c++·面试