using System.Collections; using Animancer; using UnityEngine; namespace BaseGames.Enemies.Abilities { /// /// 冲刺追击 + 体接触伤害能力:起手(Skill_Start 张口) → 朝玩家方向直冲(Skill_Loop 快速爬行 + /// 接触伤害),**直到撞墙/到平台边缘或玩家消失即停** → 收招(Skill_End)。一次冲刺为一次能力执行, /// 结束后进入冷却(EnemyAbilitySO.cooldown);AI 追击态在冷却结束时经 EnsureAbility 重触发下一次冲刺。 /// 冲刺方向在起手后按玩家所在侧一次性确定(committed 冲锋,会越过跳开的玩家,可被躲)。 /// 追击动画由本能力全权驱动(Approach 步态不抢动画)。 /// public class ContactChaseAbility : EnemyAbilityBase { [Header("动画(起手 / 循环 / 收招)")] [SerializeField] private ClipTransition _startClip; // Skill_Start:张口(一次性) [SerializeField] private ClipTransition _loopClip; // Skill_Loop:快速爬行追击(循环) [SerializeField] private ClipTransition _endClip; // Skill_End:收招(一次性) [Header("接触伤害")] [SerializeField] private BodyContactDamage _contactDamage; protected override IEnumerator ExecuteCoroutine() { Phase = AbilityRunState.Active; _enemy.SetEngaged(true); // 起手:张口(一次性)。期间静止、不伤人;锁住动画防被步态覆盖。 if (_startClip.Clip != null) { _enemy.LockAnim(_startClip.Clip.length); _animancer.Play(_startClip); yield return EnemyAbilityWaits.Get(_startClip.Clip.length); } // 冲刺方向:起手后按玩家所在侧一次性确定(committed 冲锋) float dir = (_enemy.PlayerTransform != null && _enemy.PlayerTransform.position.x < _enemy.transform.position.x) ? -1f : 1f; float speed = _enemy.Stats != null ? _enemy.Stats.RunSpeed : 0f; var mv = _enemy.Movement; // 冲刺:朝该方向直冲、接触伤害开、播 Skill_Loop,直到撞墙/到边缘或玩家消失即停。 if (_loopClip.Clip != null) _animancer.Play(_loopClip); if (_contactDamage != null) _contactDamage.enabled = true; float winX = _enemy.transform.position.x; float winT = 0f; while (_enemy.PlayerTransform != null && !(mv != null && mv.WouldBlockAhead(dir))) { _enemy.MoveInDirectionWithSpeed(dir, speed); // 速度直冲(非 nav),MoveWithSpeed 悬崖夹紧兜底 yield return null; // 兜底:撞上未在检测层的碰撞体(玩家/边界墙)导致原地不动 → 结束本次冲刺(转收招+冷却)。 // 用窗口净位移判定,避免"贴住微抖"每帧重置。 winT += Time.deltaTime; if (winT >= 0.15f) { if (Mathf.Abs(_enemy.transform.position.x - winX) < 0.05f) break; winX = _enemy.transform.position.x; winT = 0f; } } _enemy.StopMovement(); // 收招 Skill_End;随后基类按 EnemyAbilitySO.cooldown 施加冷却。 DisableContact(); yield return PlaySkillEnd(); Cleanup(); } protected override void OnInterrupted(InterruptReason reason) { DisableContact(); // 丢失目标 = AI 主动退出追击态(ExternalRequest) → 播收招 Skill_End; // 受击/死亡/硬直等硬打断不播收招。协程已停,fire-and-forget;动画锁保证 Skill_End // 在其时长内不被巡逻步态盖掉。 if (reason == InterruptReason.ExternalRequest && _endClip.Clip != null) { _enemy.LockAnim(_endClip.Clip.length); _animancer.Play(_endClip); } Cleanup(); } private IEnumerator PlaySkillEnd() { if (_endClip.Clip == null) yield break; _enemy.LockAnim(_endClip.Clip.length); _animancer.Play(_endClip); yield return EnemyAbilityWaits.Get(_endClip.Clip.length); } private void DisableContact() { if (_contactDamage != null) _contactDamage.enabled = false; } private void Cleanup() { _enemy.SetEngaged(false); _enemy.Locomotion?.Stop(); } } }