UE GAS 实战(六)完美格挡与动画分层融合
WarriorRPG 防御系统深度分析格挡完整流程图按住格挡键→ ASC 识别MustBeHeld类型输入 → 激活 Block GA蓝图 → 给角色添加Player_Status_Blocking标签敌人攻击命中时EnemyCombatComponent::OnHitTargetActor做三重判定玩家有Blocking标签敌人没有Unblockable标签IsValidBlock()两者朝向点积 -0.1面对面三条全过 → 发送SuccessfulBlock事件不扣血只播特效音效 任一不过 → 发送MeleeHit事件正常伤害流程松开格挡键→ ASC 检测到MustBeHeld释放 → 取消 Block GA → 移除Blocking标签投射物格挡同理但没有Unblockable检查远程攻击总能挡。目标锁定时格挡会暂停自动旋转让玩家自由调整朝向。没有弹反和反击机制。二、格挡系统各环节详细分析2.1 GameplayTag 定义基础设施文件WarriorGameplayTags.h/cpp防御系统依赖的 6 个核心 Tag// 输入标签InputTag_MustBeHeld// InputTag.MustBeHeld - 持续按住类父标签InputTag_MustBeHeld_Block// InputTag.MustBeHeld.Block - 格挡输入// 技能/状态/事件标签Player_Ability_Block// Player.Ability.Block - 格挡技能标识Player_Status_Blocking// Player.Status.Blocking - 格挡中状态Player_Event_SuccessfulBlock// Player.Event.SuccessfulBlock - 成功格挡事件// 敌人标签Enemy_Status_Unblockable// Enemy.Status.Unblockable - 不可格挡攻击2.2 输入处理MustBeHeld 机制文件WarriorAbilitySystemComponent.cpp格挡使用MustBeHeld持续按住模式按住激活松开取消。区别于 Toggleable按一次开/再按关和普通技能按一下触发一次。按下格挡键 → 激活 Block GAvoidUWarriorAbilitySystemComponent::OnAbilityInputPressed(constFGameplayTagInInputTag){if(!InInputTag.IsValid())return;// 遍历所有已注册技能通过 DynamicSpecSourceTags 匹配输入标签for(constFGameplayAbilitySpecAbilitySpec:GetActivatableAbilities()){if(!AbilitySpec.GetDynamicSpecSourceTags().HasTagExact(InInputTag))continue;// Toggleable 类型走切换逻辑格挡不是 Toggleable走 elseif(InInputTag.MatchesTag(WarriorGameplayTags::InputTag_Toggleable)AbilitySpec.IsActive())CancelAbilityHandle(AbilitySpec.Handle);elseTryActivateAbility(AbilitySpec.Handle);// 格挡走这里}}松开格挡键 → 取消 Block GAvoidUWarriorAbilitySystemComponent::OnAbilityInputReleased(constFGameplayTagInInputTag){// 只处理 MustBeHeld 类型普通技能松开不触发任何操作if(!InInputTag.IsValid()||!InInputTag.MatchesTag(WarriorGameplayTags::InputTag_MustBeHeld))return;for(constFGameplayAbilitySpecAbilitySpec:GetActivatableAbilities()){if(AbilitySpec.GetDynamicSpecSourceTags().HasTagExact(InInputTag)AbilitySpec.IsActive())CancelAbilityHandle(AbilitySpec.Handle);// 取消格挡 → 触发 EndAbility → 移除 Blocking 标签}}实例玩家按下右键 →Input_AbilityInputPressed(InputTag_MustBeHeld_Block)→ ASC 找到 Block GA →TryActivateAbility→ Block GA 激活并添加Player_Status_Blocking标签。松开右键 →OnAbilityInputReleased→ 检测到 MustBeHeld 类型 → 取消 Block GACancelAbilityHandle →会触发 Block GA 的 EndAbility → 移除标签。2.3 格挡判定核心是否在正面文件WarriorFunctionLibrary.cppboolUWarriorFunctionLibrary::IsValidBlock(AActor*InAttacker,AActor*InDefender){check(InAttackerInDefender);// 计算攻击者和防御者的朝向向量点积constfloatDotResultFVector::DotProduct(InAttacker-GetActorForwardVector(),InDefender-GetActorForwardVector());constfloatThreshold-0.1f;// 点积 -0.1 时格挡有效两者面对面returnDotResultThreshold;}通俗解释想象两个人站在一起每个人面朝一个方向。点积可以告诉我们他们的朝向关系点积 1完全同向都朝同一方向看玩家背对敌人→ 格挡无效点积 0互相垂直侧对侧→ 格挡无效点积 -1完全相对面对面→ 格挡有效阈值-0.1意味着偏差角度约84°以内都算有效格挡这给了玩家较大的容错空间。格挡有效区域约168°的正面弧度 敌人 ↓ ← ← ↓ → → / \ / 格挡有效区 \ / \ 玩家 ←——————→ 玩家朝向 \ / \ 格挡有效区 / \ / → → ↑ ← ←2.4 近战攻击的格挡判定文件EnemyCombatComponent.cpp当敌人的武器/拳头碰撞盒与玩家重叠时触发voidUEnemyCombatComponent::OnHitTargetActor(AActor*HitActor){// 去重 if(OverlappedActors.Contains(HitActor))return;OverlappedActors.AddUnique(HitActor);boolbIsValidBlockfalse;// 三重判定 // ① 玩家是否正在格挡constboolbIsPlayerBlockingUWarriorFunctionLibrary::NativeDoesActorHaveTag(HitActor,WarriorGameplayTags::Player_Status_Blocking);// ② 敌人攻击是否不可格挡constboolbIsMyAttackUnblockableUWarriorFunctionLibrary::NativeDoesActorHaveTag(GetOwningPawn(),WarriorGameplayTags::Enemy_Status_Unblockable);// ③ 方向判定仅在①②通过后if(bIsPlayerBlocking!bIsMyAttackUnblockable)bIsValidBlockUWarriorFunctionLibrary::IsValidBlock(GetOwningPawn(),HitActor);// 结果分发 FGameplayEventData EventData;EventData.InstigatorGetOwningPawn();EventData.TargetHitActor;if(bIsValidBlock)// 格挡成功 → 不触发伤害发送成功格挡事件给玩家UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(HitActor,WarriorGameplayTags::Player_Event_SuccessfulBlock,EventData);else// 格挡失败 → 正常伤害流程UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(GetOwningPawn(),WarriorGameplayTags::Shared_Event_MeleeHit,EventData);}关键设计格挡成功时事件发送给玩家HitActor格挡失败时事件发送给敌人自身GetOwningPawn()。这是因为伤害计算 GA 监听的是Shared_Event_MeleeHit该 GA 绑定在攻击者身上。2.5 远程攻击的格挡判定文件WarriorProjectileBase.cpp远程攻击的格挡逻辑与近战类似但有两个不同没有 Unblockable 检查远程攻击总是可以被格挡只要方向正确攻击者是投射物本身IsValidBlock(this, HitPawn)用投射物的朝向飞行方向计算// 投射物命中回调中的格挡检查简化boolbIsValidBlockfalse;constboolbIsPlayerBlockingUWarriorFunctionLibrary::NativeDoesActorHaveTag(HitPawn,WarriorGameplayTags::Player_Status_Blocking);if(bIsPlayerBlocking)bIsValidBlockUWarriorFunctionLibrary::IsValidBlock(this,HitPawn);// this 投射物if(bIsValidBlock)// 格挡成功不造成伤害SendGameplayEventToActor(HitPawn,Player_Event_SuccessfulBlock,Data);else// 正常伤害HandleApplyProjectileDamage(HitPawn,Data);2.6 目标锁定中的格挡特殊处理文件HeroGameplayAbility_TargetLock.cpp在目标锁定状态下每帧会强制旋转角色朝向锁定目标。但格挡时需要允许玩家自由调整朝向以便面向其他方向的敌人格挡因此做了特殊处理voidUHeroGameplayAbility_TargetLock::OnTargetLockTick(floatDeltaTime){// ...// 格挡或翻滚时不强制旋转constboolbShouldOverrideRotation!UWarriorFunctionLibrary::NativeDoesActorHaveTag(GetHeroCharacterFromActorInfo(),WarriorGameplayTags::Player_Status_Rolling)!UWarriorFunctionLibrary::NativeDoesActorHaveTag(GetHeroCharacterFromActorInfo(),WarriorGameplayTags::Player_Status_Blocking);if(bShouldOverrideRotation){// 正常旋转朝向目标...}// 格挡时不旋转玩家可自由调整朝向以应对不同方向的攻击}防御表演这是 Sequence 的 Then 0第一件事做的是让英雄角色立即转向攻击者。格挡成功瞬间角色被推动微微后退一小步0.2秒力度80模拟被敌人攻击击退的格挡后震动感以及在角色身上播放魔法盾成功格挡的视觉特效和音效火花、金属碰撞声等。三、完美格挡概述按下格挡键的瞬间挡住敌人攻击 完美格挡实现逻辑奖励慢动作演出 华丽特效 直接跳到终结技。具体如下Branch: Is Perfect Block?├─ False → 什么都不做普通格挡到此结束└─ True → 执行以下完美格挡专属流程 ↓① Add Gameplay Tag to Actor if NoneIn Actor: Hero CharacterTag to Add: Player.Status.JumpToFinisher→ 给英雄添加可跳转终结技状态标签② Execute GameplayCue With Params On OwnerGameplay Cue Tag: GameplayCue.FX.MagicShield.PerfectBlock→ 播放完美格挡专属特效比普通格挡更华丽的魔法盾闪光③ Set Global Time Dilation 0.2→ 全局时间缩放到 20%慢动作④ Delay: Duration 0.08秒→ 维持慢动作 0.08 秒实际体感约 0.4 秒因为时间变慢了⑤ Set Global Time Dilation 1.0→ 恢复正常速度⑥ Start Reset Jump to Finisher TimerTarget: self (GA_Hero_Block)→ 启动一个计时器超时后自动移除 JumpToFinisher 标签总结完美格挡 普通格挡的所有反馈 三个额外奖励奖励效果终结技窗口添加Player.Status.JumpToFinisher标签在限定时间内按攻击可以触发终结技这就是之前问的格挡后特殊攻击慢动作演出全局时间降到 0.2 倍速持续约 0.08 秒制造子弹时间的戏剧性停顿感华丽特效播放PerfectBlock专属特效比普通SuccessfulBlock更炫四、防御反击Counter Attack系统分析完美格挡后 → 会设定可以跳转到终结技能的tag然后攻击的ga里会判定是否有这个tag如果有这个tag则下一次攻击是终结攻击即可这个tag会在短暂后清除敌人的无视防御的攻击不可格挡Unblockable判定实现逻辑一句话总结敌人的特定攻击技能在激活时临时给自身贴上Unblockable标签碰撞判定时检查这个标签决定能不能被格挡技能结束后撕掉标签。[敌人 AI 行为树选中一个不可格挡攻击] ↓ 敌人攻击 GA 激活如 GA_Giant_MeleeAttack_3 蓄力重击 ├─ AddTag: Enemy.Status.Unblockable → 给自身贴上标签 ↓ [碰撞盒碰到玩家] → EnemyCombatComponent::OnHitTargetActor ├─ 玩家有 Blocking 标签 ✓玩家在格挡 ├─ 敌人有 Unblockable 标签 ✓ ← 关键检查 → 玩家格挡无效照样受伤 ↓ 攻击 GA 结束 └─ RemoveTag: Enemy.Status.Unblockable → 撕掉标签::: info 提问为什么是这样的逻辑按理来说应该是某个技能不可格挡而非给怪物添加不可格挡的逻辑吧:::是的从设计角度看某个技能不可格挡比整个怪物不可格挡更合理。EnemyCombatComponent::OnHitTargetActor做格挡判定时它能直接访问的只有敌人 Pawn和被打的玩家这两个 Actor。想要获取ga本身是比较麻烦的通过tag可以简单一点。五、动画混合播放格挡动画时设定了上半身的插槽在玩家的动画里实现了分层混合这样可以实现上半身防御下半身走路