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)
{