本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 ------ 《P61 开火蒙太奇(Fire Montage)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author)Stephen Ulibarri 发布在 Udemy 上的课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
文章目录
- [P61 开火蒙太奇(Fire Montage)](#P61 开火蒙太奇(Fire Montage))
- [61.1 开火操作映射](#61.1 开火操作映射)
- [61.2 创建开火动画蒙太奇](#61.2 创建开火动画蒙太奇)
- [61.3 播放蒙太奇](#61.3 播放蒙太奇)
- [61.4 Summary](#61.4 Summary)
P61 开火蒙太奇(Fire Montage)
本节课我们想要使用武器进行开火,这意味着我们需要创建用于开火的输入动作映射和开火的动画蒙太奇。
61.1 开火操作映射
-
添加开火操作映射 "
Fire
",绑定按键为鼠标左键。
-
打开 Visual Studio,在 "
BlasterCharacter.h
" 声明 "Fire
" 操作映射函数 "FireButtonPressed()
" 和 "FireButtonReleased()
",用于实现人物角色持枪开火和停火。cpp/*** BlasterCharacter.h ***/ ... UCLASS() class BLASTER_API ABlasterCharacter : public ACharacter { GENERATED_BODY() ... protected: // Called when the game starts or when spawned virtual void BeginPlay() override; // 与轴映射相对应的回调函数 void MoveForward(float Value); // 人物角色前进或后退 void MoveRight(float Value); // 人物角色左移或右移 void Turn(float Value); // 人物角色视角左转或右转 void LookUp(float Value); // 人物角色俯视或仰视 // 与动作映射相对应的回调函数 void EquipButtonPressed(); // 人物角色装备武器 void CrouchButtonPressed(); // 人物角色蹲伏 void AimButtonPressed(); // 人物角色开始瞄准 void AimButtonReleased(); // 人物角色停止瞄准 void AimOffset(float DeltaTime); // 人物角色瞄准偏移 virtual void Jump() override; // 人物角色跳跃 /* P61 开火蒙太奇(Fire Montage)*/ void FireButtonPressed(); // 人物角色开火 void FireButtonReleased(); // 人物角色停火 /* P61 开火蒙太奇(Fire Montage)*/
-
在 "
CombatComponent.h
" 中声明变量 "bIsFireButtonPressed
",用于记录人物角色是否在开火,这样我们在动画蓝图就可以使用它的值判断是否播放开火动画,同时声明函数 "FireButtonPressed()
" 并在 "CombatComponent.cpp
" 中实现,用于开火或停火操作时设置 "bIsFireButtonPressed
" 的值。cpp/*** CombatComponent.h ***/ UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class BLASTER_API UCombatComponent : public UActorComponent { GENERATED_BODY() ... protected: ... UFUNCTION() void OnRep_EquippedWeapon(); // 设置人物角色朝向的 Repnotify 函数 /* P61 开火蒙太奇(Fire Montage)*/ void FireButtonPressed(bool bPressed); // 根据人物角色是否在开火,设置 bIsFireButtonPressed /* P61 开火蒙太奇(Fire Montage)*/ private: ... UPROPERTY(EditAnywhere) float BaseWalkSpeed; // 基础行走速度 UPROPERTY(EditAnywhere) float AimWalkSpeed; // 瞄准行走速度 /* P61 开火蒙太奇(Fire Montage)*/ bool bIsFireButtonPressed; // 人物角色开火键是否按下 /* P61 开火蒙太奇(Fire Montage)*/ };
cpp/*** CombatComponent.cpp ***/ ... /* P61 开火蒙太奇(Fire Montage)*/ // 根据人物角色是否在开火,设置 bIsFireButtonPressed void UCombatComponent::FireButtonPressed(bool bPressed) { bIsFireButtonPressed = bPressed; } /* P61 开火蒙太奇(Fire Montage)*/
-
在 "
BlasterCharacter.cpp
" 的函数 "SetupPlayerInputComponent()
" 中分别绑定操作映射函数 "FireButtonPressed()
" 和 "FireButtonReleased()
",并完成它们的定义。cpp/*** 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); PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &ABlasterCharacter::CrouchButtonPressed); PlayerInputComponent->BindAction("Aim", IE_Pressed, this, &ABlasterCharacter::AimButtonPressed); PlayerInputComponent->BindAction("Aim", IE_Released, this, &ABlasterCharacter::AimButtonReleased); /* P61 开火蒙太奇(Fire Montage)*/ PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &ABlasterCharacter::FireButtonPressed); PlayerInputComponent->BindAction("Fire", IE_Released, this, &ABlasterCharacter::FireButtonReleased); /* P61 开火蒙太奇(Fire Montage)*/ ... } ... /* P61 开火蒙太奇(Fire Montage)*/ // 人物角色开火键按下 void ABlasterCharacter::FireButtonPressed() { if (Combat) Combat->FireButtonPressed(true); // 人物角色在开火,设置 Combat->bIsFireButtonPressed 为 true } // 人物角色开火键松开 void ABlasterCharacter::FireButtonReleased() { if (Combat) Combat->FireButtonPressed(false); // 人物角色已停火,设置 Combat->bIsFireButtonPressed 为 false } /* P61 开火蒙太奇(Fire Montage)*/
61.2 创建开火动画蒙太奇
-
编译后,在虚幻引擎中设置开火动画 "
Fire_Rifle_Hip
" 和瞄准开火动画 "Fire_Rifle_Ironsights
" 的基础姿势动画为 "Zero_Pose
" 和 "Aim_Zero_Pose
"
-
基于 "
Fire_Rifle_Hip
" 创建动画蒙太奇 "FireWeapon
",并移动至 "Game/Blueprints/Character/Animation/
" 目录下。
-
打开 "
FireWeapon
" 动画蒙太奇编辑窗口,"新建蒙太奇片段 "(New Montage Section),命名为 "RifleHip
",然后 "删除蒙太奇片段 "(Delete Montage Section)"Default
"。
-
打开 "动画插槽管理器 "(Animation Slot Manager)添加新的插槽 "
WeaponSlot
",这样我们就可以在动画姿势中使用这个蒙太奇。
-
在资产编辑器中切换 "
DefaultGroup
" 的插槽为 "WeaponSlot
"。注意这里需要在左侧资产详情面板设置 "FireWeapon
" 预览基础姿势为 "Zero_Pose
",否则在视口中人物角色还是 A 姿势;这个是教学视频里没提到的如果设置了预览基础姿势依然是 A 姿势,可以尝试点击 "预览插槽 "(Preview Slot)。
-
拖拽 "
Fire_Rifle_Ironsights
" 至资产编辑器中,新建蒙太奇片段 "RifleAim
",在右下角蒙太奇片段中选中 "清除所有 "(Clear),这样 "RifleHip
" 就不会直接过渡到 "RifleAim
" 了。
61.3 播放蒙太奇
-
在 Visual Studio 中打开 "
BlasterCharacter.h
",声明动画蒙太奇变量 "FireWeaponMontage
" 和函数 "PlayFireMontage()
",在 "BlasterCharacter.cpp
" 中完成函数 "PlayFireMontage()
" 的定义。cpp/*** BlasterCharacter.h ***/ ... class BLASTER_API ABlasterCharacter : public ACharacter { GENERATED_BODY() public: ... // 重写复制属性函数 virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void PostInitializeComponents() override; /* P61 开火蒙太奇(Fire Montage)*/ // 播放开火蒙太奇 void PlayFireMontage(bool bAiming); /* P61 开火蒙太奇(Fire Montage)*/ ... };
cpp/*** BlasterCharacter.cpp ***/ ... /* P61 开火蒙太奇(Fire Montage)*/ #include "BlasterAnimInstance.h" /* P61 开火蒙太奇(Fire Montage)*/ ... /* P61 开火蒙太奇(Fire Montage)*/ // 播放开火蒙太奇 void ABlasterCharacter::PlayFireMontage(bool bAiming) { if (Combat == nullptr || Combat->EquippedWeapon == nullptr) return; UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance(); // 获取动画实例 if (AnimInstance && FireWeaponMontage) { AnimInstance->Montage_Play(FireWeaponMontage); // 在动画实例上播放开火蒙太奇 FName SectionName; // 蒙太奇片段名 SectionName = bAiming ? FName("RifleAim") : FName("RifleHip"); // 如果人物角色在瞄准,播放 RifleAiming,否则播放 RifleHip AnimInstance->Montage_JumpToSection(SectionName); // 在动画实例上播放蒙太奇片段 } } /* P61 开火蒙太奇(Fire Montage)*/
-
编译后,在虚幻引擎中打开 "
BP_EpicCharacter
",在右侧细节面板中设置 "Combat
" 下的 "Fire Weapon Montage
" 为 "FireWeapon
"。
-
打开 "
BlasterAnimBP
",在 AnimGraph 面板添加蓝图节点 "使用缓存姿势 "Equipped" "、"插槽 "DefaulSlot" " 节点以及 "新保存的缓存姿势 "节点,并绘制下图所示蓝图。然后,选中 "插槽 "DefaulSlot" " 节点,在右侧细节面板切换插槽为 "WeaponSlot
"。
-
在 "
AimOffset
" 状态机的 "AimOffset
" 状态节点蓝图面板中添加 "使用缓存姿势 "WeaponSlot" ",按下图修改蓝图。
-
在 Visual Studio 中打开 "
CombatComponent.cpp
" 完善函数 "FireButtonPressed()
" 的定义,如果按下开火键,调用 "PlayFireMontage
" 函数,根据人物角色的瞄准状态播放蒙太奇片段。cpp/*** BlasterCharacter.cpp ***/ /* P61 开火蒙太奇(Fire Montage)*/ // 根据人物角色是否在开火,设置 bIsFireButtonPressed;根据人物角色的瞄准状态,播放开火蒙太奇片段 void UCombatComponent::FireButtonPressed(bool bPressed) { bIsFireButtonPressed = bPressed; // 设置 bIsFireButtonPressed if (Character && bIsFireButtonPressed) // 如果人物角色在开火 { Character->PlayFireMontage(bAiming); // 根据 bAming 的值播放开火蒙太奇片段 } } /* P61 开火蒙太奇(Fire Montage)*/
-
编译后在虚幻引擎中打开 "
FireWeapon
" 动画蒙太奇编辑窗口,在左侧资产详情面板将 "混入 "(Blend In)和 "混出 "(Blend Out)选项卡下的 "混合时间 " 都设置为 0.0。 -
进行测试,我们操控人物角色拾起武器,在不瞄准和瞄准两种情况下按下开火键,可以看到开火动画蒙太奇可以正常播放。
61.4 Summary
本节课我们成功实现了角色开火功能,包括输入处理、动画蒙太奇的创建和播放。在输入处理方面,我们在项目设置中创建 "Fire
" 开火操作映射,绑定到鼠标左键。接着,在BlasterCharacter
中定义了操作映射函数 "FireButtonPressed()
" 和 "FireButtonReleased()
" ,在 "CombatComponent
" 中添加了用于记录开火键是否按下的变量 "bIsFireButtonPressed
" 以及它对应的设置函数。随后,我们设置好 "Fire_Rifle_Hip
" 和 "Fire_Rifle_Ironsights
" 的基础姿势,基于开火动画创建"FireWeapon
" 动画蒙太奇,并为 "FireWeapon
" 添加了 "RifleHip
" 和 "RifleAim
" 两个蒙太奇片段,同时还创建了 "WeaponSlot
" 插槽用于动画混合。接下来,我们进行了动画蓝图的优化,添加插槽节点和缓存姿势,在瞄准偏移状态机中集成武器插槽。最后,我们实现了蒙太奇的播放逻辑,在 "BlasterCharacter
" 中实现 "PlayFireMontage()
" 函数,根据瞄准状态播放对应的蒙太奇片段,在角色蓝图中设置开火蒙太奇资源;为确保动画立即播放,设置蒙太奇的混合时间为 0。测试结果表明我们的人物角色能够在站立和瞄准状态下正确播放对应的开火动画,为后续的武器射击功能奠定了基础。