03 近战连招系统

上一章:[[02-英雄角色与基础框架]] 返回:[[00-课程索引]] 下一章:[[04-英雄战斗与反馈UI]]

[!info] 本章边界
C++ 负责武器注册、输入路由、AbilitySpec 授予/撤销和动画层访问;轻重攻击连招主体位于 Gameplay Ability 与 Montage 蓝图资产。未能从二进制资产验证的节点、变量和 Section 名不会臆造。

总体链路

1
2
3
4
5
Input Action → InputTag → ASC DynamicAbilityTags → Gameplay Ability
Gameplay Ability → Montage → Gameplay Event → 装备/连招阶段
Weapon Data → Anim Layer + Mapping Context + Default Abilities
Equip → GiveAbility并保存Handle
Unequip → ClearAbility并清空Handle

P23 Melee Combo System Section Overview(01:50)

章节概览,无独立提交。目标是建立“武器提供能力,ASC 执行能力,Montage 决定节奏”的数据驱动结构。

P24 Challenges Ahead(03:43)

练习课,无独立提交。需要先拆解:组件如何取得 Pawn、武器如何按 Tag 注册、输入如何映射 Ability、装备能力如何撤销、动画层如何切换、连招如何在中断时重置。

P25 Pawn Extension Component(10:29)

提交ac23f0f

1
2
3
4
UActorComponent
└─ UPawnExtensionComponentBase
└─ UPawnCombatComponent
└─ UHeroCombatComponent
1
2
3
4
5
6
template<class T>
T* GetOwningPawn() const
{
static_assert(TPointerIsConvertibleFromTo<T, APawn>::Value);
return CastChecked<T>(GetOwner());
}
  • static_assert 在编译期约束模板参数。
  • CastChecked 类型错误时触发检查,并非静默返回空。
  • AWarriorHeroCharacter 以默认子对象持有 HeroCombatComponent

P26 Register Spawned Weapon(17:14)

提交fcc7f0c

1
2
TMap<FGameplayTag, AWarriorWeaponBase*> CharacterCarriedWeaponMap;
FGameplayTag CurrentEquippedWeaponTag;
1
2
3
4
5
void RegisterSpawnedWeapon(FGameplayTag WeaponTag,
AWarriorWeaponBase* Weapon,
bool bRegisterAsEquippedWeapon);
AWarriorWeaponBase* GetCharacterCarriedWeaponByTag(FGameplayTag WeaponTag) const;
AWarriorWeaponBase* GetCharacterCurrentEquippedWeapon() const;

注册时检查重复 Tag 和空武器,再 Emplace。本节加入 Player.Weapon.Axe。蓝图 GA_Hero_SpawnAxe 生成并附着 BP_HeroAxe 后调用注册函数。

P27 Hero Gameplay Ability(10:35)

提交4abe8da

1
class UWarriorHeroGameplayAbility : public UWarriorGameplayAbility

提供:

1
2
3
AWarriorHeroCharacter* GetHeroCharacterFromActorInfo();
AWarriorHeroController* GetHeroControllerFromActorInfo();
UHeroCombatComponent* GetHeroCombatComponentFromActorInfo();

角色和 Controller 用 TWeakObjectPtr 缓存,初次从 CurrentActorInfo 转换。注意 Combat Component 访问器会继续解引用 Hero,调用前仍应确保 ActorInfo 类型正确。

P28 Ability Input Action(08:32)

提交a0828f2

UDataAsset_InputConfig 新增:

1
TArray<FWarriorInputActionConfig> AbilityInputActions;

UWarriorInputComponent 新增批量绑定:

1
2
BindAction(Action, ETriggerEvent::Started, Context, PressedFunc, InputTag);
BindAction(Action, ETriggerEvent::Completed, Context, ReleasedFunc, InputTag);

本课程实际绑定 StartedCompleted,不是直接绑定 Held。新增 IA_EquipAxeInputTag.EquipAxe

P29 Hero Ability Set(10:26)

提交9ae5e50

1
2
3
4
5
struct FWarriorHeroAbilitySet
{
FGameplayTag InputTag;
TSubclassOf<UWarriorHeroGameplayAbility> AbilityToGrant;
};

UDataAsset_HeroStartUpData::GiveToAbilitySystemComponent

1
2
3
4
5
FGameplayAbilitySpec Spec(AbilitySet.AbilityToGrant);
Spec.SourceObject = InASCToGive->GetAvatarActor();
Spec.Level = ApplyLevel;
Spec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);
InASCToGive->GiveAbility(Spec);

关键设计:输入 Tag 放在 AbilitySpec.DynamicAbilityTags,同一 Ability 类可以由不同配置映射不同输入。

P30 Binding Ability Input(08:59)

提交259ff81

1
2
3
4
5
6
Ability Input Started
→ HeroCharacter::Input_AbilityInputPressed(Tag)
→ WarriorASC::OnAbilityInputPressed(Tag)
→ 遍历 GetActivatableAbilities
→ DynamicAbilityTags.HasTagExact(Tag)
→ TryActivateAbility(Spec.Handle)

松开输入转发给 OnAbilityInputReleased。后续按住能力通过 InputTag.MustBeHeld 层级决定是否取消;P30 首要目标是 Tag 到 AbilitySpec 的路由。

P31 Play Montage and Wait(07:30)

提交47b6a38;蓝图/动画资产课。

资产:GA_Hero_EquipAxeAM_Hero_Axe_EquipABP_Hero

1
Ability 激活 → Play Montage And Wait → 等待 Completed/Interrupted/Cancelled

该 Ability Task 自动协调能力与 Montage 生命周期。二进制资产无法确认每个回调的节点连接,笔记实践时应确保四类退出路径最终都清理状态并结束 Ability。

P32 Wait for Gameplay Event(12:01)

提交a71660e

新增 Tag:Player.Event.Equip.Axe;资产 AN_SendGameplayEventToOwner

1
2
3
4
5
AM_Hero_Axe_Equip 的 AnimNotify
→ Send Gameplay Event To Owner
→ Player.Event.Equip.Axe
→ GA_Hero_EquipAxe 的 Wait Gameplay Event 被唤醒
→ 执行真正的装备阶段

这把“何时装备”交给动画帧,而不是写死 Delay。

P33 Animation Layer Interface(10:57)

提交1b263e2;资产课。

  • ALI_Hero:动画层接口。
  • ABP_Hero:主英雄 AnimBP 接入接口。
  • GA_Hero_EquipAxe:装备流程开始关联动画层。

接口只定义姿势契约;具体武器动画层实现同一组函数,使角色主状态机无需复制。

P34 Master Anim Layer(09:06)

提交442ee19

资产:MasterAnimLayer_HeroAnimLayer_HeroAxeBS_DefaultLocomotion_Axe

C++:

1
2
3
4
UWarriorHeroAnimInstance* UWarriorHeroLinkedAnimLayer::GetHeroAnimInstance() const
{
return Cast<UWarriorHeroAnimInstance>(GetOwningComponent()->GetAnimInstance());
}

Linked Layer 由此读取主 AnimInstance 的速度、状态等数据,避免重复计算。

提交c5419f8

FWarriorHeroWeaponData 开始承载:

1
2
TSubclassOf<UWarriorHeroLinkedAnimLayer> WeaponAnimLayerToLink;
UInputMappingContext* WeaponInputMappingContext;
1
2
3
4
5
GA_Hero_EquipAxe
→ 当前 Weapon Tag 查询 Hero Weapon
→ 读取 HeroWeaponData.WeaponAnimLayerToLink
→ 链接斧头动画层
→ 后续卸下时恢复默认层

P36 Default Weapon Ability(18:16)

提交183e6b1

FWarriorHeroWeaponData 新增:

1
TArray<FWarriorHeroAbilitySet> DefaultWeaponAbilities;

新增资源:GA_Hero_UnequipAxeIA_UnequipAxeAM_Hero_Axe_Unequip;Tag:

1
2
3
InputTag.UnequipAxe
Player.Ability.Unequip.Axe
Player.Event.Unequip.Axe

武器数据现在描述动画层、专属 Mapping Context 与默认能力,角色类不硬编码斧头技能。

P37 Grant Weapon Ability(12:57)

提交演进ebec211,最终收敛于 8170adc

1
2
3
4
5
void UWarriorAbilitySystemComponent::GrantHeroWeaponAbilities(
const TArray<FWarriorHeroAbilitySet>& DefaultAbilities,
...,
int32 ApplyLevel,
TArray<FGameplayAbilitySpecHandle>& OutHandles);
1
2
3
4
Spec.SourceObject = GetAvatarActor();
Spec.Level = ApplyLevel;
Spec.DynamicAbilityTags.AddTag(AbilitySet.InputTag);
OutHandles.AddUnique(GiveAbility(Spec));

[!important] 最终源码的 SourceObject 是 Avatar Actor,不是 Weapon。武器数据通过 Hero Combat Component 查询。

装备时保存所有返回的 FGameplayAbilitySpecHandle,为卸下精确撤销做准备。

P38 Handle Unequip Axe(14:30)

提交演进17800f9,最终收敛于 e1abec3

AWarriorHeroWeapon 保存:

1
TArray<FGameplayAbilitySpecHandle> GrantedAbilitySpecHandles;

ASC 撤销函数实际拼写为:

1
RemovedGrantedHeroWeaponAbilities(TArray<FGameplayAbilitySpecHandle>& Handles)

逻辑逐个 ClearAbility,随后 Handles.Empty()。卸下蓝图还需解除动画层、移除武器 Mapping Context 并更新装备状态。

P39 Light Attack Ability(07:34)

提交b3aab62;主要为 Ability/输入资产。

1
2
3
4
5
IA_LightAttack_Axe
→ InputTag.LightAttack.Axe
→ Player.Ability.Attack.Light.Axe
→ GA_Hero_LightAttack_Axe
→ GA_Hero_LightAttackMaster

BP_HeroAxe.DefaultWeaponAbilities 配置轻攻击能力;装备时授予,卸下时移除。没有轻攻击专用 C++ Ability 类。

P40 Combo Logic(14:09)

提交bd50a96;纯蓝图/动画编排。

资产包含 4 段轻攻击 Montage:AM_Hero_Axe_LightAttack_1..4,由 Master Ability 组织连招。

可确认的结构:

1
2
3
4
5
重复 LightAttack 输入
→ 同一个动态输入 Tag 激活/通知轻攻击 Ability
→ Master Ability 管理当前攻击段
→ 播放对应 Montage
→ 动画窗口决定是否接受下一段

具体缓存变量名、Section 名和 Notify 时间点无法从二进制资产验证。

P41 Heavy Attack Ability(05:55)

提交ea7f377

1
2
3
4
5
IA_HeavyAttack_Axe
→ InputTag.HeavyAttack.Axe
→ Player.Ability.Attack.Heavy.Axe
→ GA_Hero_HeavyAttack_Axe
→ GA_Hero_HeavyAttackMaster

能力被加入 BP_HeroAxe 的默认武器能力数组,复用同一输入和 AbilitySpec 管线。

P42 Heavy Attack Logic(08:29)

提交d3fbe0a;资产课。

  • AM_Hero_Axe_HeavyAttack_1
  • AM_Hero_Axe_HeavyAttack_2
  • Heavy Attack Master/Axe Ability

课程提交可确认两段重攻击流程;是否采用蓄力、Section 或哪种事件分支,必须以实际蓝图图表为准。

P43 Warrior Function Library(17:15)

提交7eecd3f

1
class UWarriorFunctionLibrary : public UBlueprintFunctionLibrary

本节核心函数:

1
2
3
4
5
NativeGetWarriorASCFromActor(AActor* Actor);
AddGameplayTagToActorIfNone(AActor* Actor, FGameplayTag Tag);
RemoveGameplayTagFromActorIfFound(AActor* Actor, FGameplayTag Tag);
NativeDoesActorHaveTag(AActor* Actor, FGameplayTag Tag);
BP_DoesActorHaveTag(..., EWarriorConfirmType& OutConfirmType);
1
2
Actor → AbilitySystemBlueprintLibrary::GetAbilitySystemComponent
→ Warrior ASC → Has/Add/Remove Loose GameplayTag

函数库无状态,向蓝图提供统一的 GAS 查询和分支节点。

P44 Jump To Finisher(08:14)

提交945c8e4

新增 Tag:Player.Status.JumpToFinisher

1
2
3
轻攻击流程设置/判断 JumpToFinisher 状态
→ 重攻击输入读取该状态
→ 进入终结技分支

提交证明状态 Tag 与能力资产发生变更;具体 Montage Section 和跳转节点无法从二进制资产确认。

P45 Slow Motion and Sound FX(14:31)

提交47e654f;动画资产课。

新增 ANS_SlowMotion,并修改装备、卸下、轻/重攻击 Montage。Anim Notify State 将慢动作绑定到动画区间,Begin/End 分别启用和恢复效果;具体 Time Dilation 值、音效资源和实现节点不可从当前源码验证。

P46 Section Wrap Up(03:50)

提交661ba3d ComboWrapUpDone

本章最终闭环:

1
2
3
4
5
6
7
Startup Data 授予装备 Ability
→ 装备输入激活 Ability
→ Montage Event 对齐装备帧
→ 注册武器、链接 Anim Layer、添加 IMC_Axe
→ 授予武器默认能力并保存 SpecHandle
→ 轻/重攻击输入激活对应 Ability
→ 卸下时 ClearAbility、移除映射、解除动画层

本章关键 Tag

1
2
3
4
5
6
7
Player.Weapon.Axe
InputTag.EquipAxe / UnequipAxe
InputTag.LightAttack.Axe / HeavyAttack.Axe
Player.Ability.Equip.Axe / Unequip.Axe
Player.Ability.Attack.Light.Axe / Heavy.Axe
Player.Event.Equip.Axe / Unequip.Axe
Player.Status.JumpToFinisher

源码审计提醒

  • BindAbilityInputAction 实际绑定 Started/Completed
  • GrantHeroWeaponAbilitiesSourceObject 是 Avatar。
  • CastChecked 不是静默安全失败。
  • 当前最终源码部分函数有历史拼写,如 RemovedGrantedHeroWeaponAbilities
  • 连招变量和 Montage Section 名必须打开对应 Ability/Montage 资产核对,不能凭标题杜撰。

本文从 LearnByCompany 原始文档 自动同步。