diff --git a/Assets/_Game/Scripts/Enemies/Abilities/RushAbility.cs b/Assets/_Game/Scripts/Enemies/Abilities/RushAbility.cs index 814bc0d3..839f7c8b 100644 --- a/Assets/_Game/Scripts/Enemies/Abilities/RushAbility.cs +++ b/Assets/_Game/Scripts/Enemies/Abilities/RushAbility.cs @@ -21,6 +21,9 @@ namespace BaseGames.Enemies.Abilities /// 悬崖:冲锋期间**允许越过崖沿**(经 放开移动层的悬崖夹紧), /// 敌人会冲出平台边缘并按重力下落、空中保持水平冲速,而不是停在崖边悬空。 /// + /// 收尾不变量:冲锋结束时**绝不允许**停在"着地但身体中心悬在崖外"的姿态—— + /// 该姿态会让导航无法把敌人映射到可行走段,导致巡逻/寻路失效。由 SettleRoutine 保证(见其说明)。 + /// /// 冲锋动画由本能力全权驱动(步态不抢动画)。身体接触伤害与本能力解耦: /// 由常驻的 全程负责,本能力不开关它。 /// @@ -32,6 +35,9 @@ namespace BaseGames.Enemies.Abilities private const float StuckWindowSeconds = 0.15f; private const float StuckMinProgress = 0.05f; + // 落位收敛的最长额外推进时长(秒):用于脱离"中心悬在崖外"的非法收尾姿态 + private const float SettleMaxSeconds = 0.5f; + protected override void Awake() { base.Awake(); @@ -78,6 +84,7 @@ namespace BaseGames.Enemies.Abilities _animancer.Play(_cfg.loopClip); yield return RushRoutine(dir, lockX, speed, mv); + yield return SettleRoutine(dir, speed, mv); // 收尾姿态必须合法:中心不得悬在崖外 _enemy.StopMovement(); @@ -126,6 +133,30 @@ namespace BaseGames.Enemies.Abilities } } + /// + /// 落位收敛:**禁止**冲锋以"着地但身体中心悬在崖外"的姿态收尾。 + /// 该姿态下碰撞体后半截压在平台角上、物理会把敌人托住(刚体冻结旋转),看似停稳, + /// 但导航无法把此位置映射到可行走段,会让巡逻/寻路彻底失效。 + /// 处理:继续朝原方向推进,直到离地(转入自由落体,随后正常落地)或中心重回地面上方。 + /// 有时限;若到时仍未脱离(多为崖沿处有墙把敌人卡住的地形问题),显式告警暴露根因,不静默留下坏站位。 + /// + private IEnumerator SettleRoutine(float dir, float speed, EnemyMovement mv) + { + if (mv == null) yield break; + + float t = 0f; + while (mv.IsGrounded && !mv.IsCenterOverGround() && t < SettleMaxSeconds) + { + _enemy.MoveInDirectionWithSpeed(dir, speed, allowLedgeCross: true); + yield return null; + t += Time.deltaTime; + } + + if (mv.IsGrounded && !mv.IsCenterOverGround()) + Debug.LogWarning($"[RushAbility] '{_enemy.name}' 冲锋收尾时身体中心悬在崖外且无法脱离" + + "(多为崖沿处存在墙体阻挡)。该站位会使导航无法映射到可行走段,巡逻将失效——请检查该处地形。", this); + } + protected override void OnInterrupted(InterruptReason reason) { // 丢失目标 = AI 主动退出(ExternalRequest) → 播收招 Skill_End; diff --git a/Assets/_Game/Scripts/Enemies/EnemyMovement.cs b/Assets/_Game/Scripts/Enemies/EnemyMovement.cs index 85b55bed..958c8a36 100644 --- a/Assets/_Game/Scripts/Enemies/EnemyMovement.cs +++ b/Assets/_Game/Scripts/Enemies/EnemyMovement.cs @@ -251,6 +251,20 @@ namespace BaseGames.Enemies new Vector2(sign, 0f), _wallCheckDist, _wallMask); } + /// + /// 身体中心正下方是否有地面。 + /// 用于禁止"中心悬在崖外"的停留姿态:碰撞体后半截压在平台角上时物理仍会托住敌人 + /// (刚体冻结旋转不会翻下去),但导航无法把该位置映射到可行走段,会导致巡逻/寻路失效。 + /// 因此任何"停下来"的收尾都必须满足本条件(或已离地处于自由落体中)。 + /// + public bool IsCenterOverGround() + { + var body = Body; + if (body == null || body.Collider == null) return true; // 无身体几何时不做限制(错误已由 EnemyBase 报出) + Bounds b = body.Bounds; + return Physics2D.Raycast(new Vector2(b.center.x, b.min.y), Vector2.down, _groundCheckDist, _groundMask); + } + /// 前缘外是否为悬崖(脚边地面缺失,会让碰撞体越出地形边缘)。 public bool WouldFallAhead(float dir) {