feat(enemy): 追击(nav)也夹紧悬崖边缘——碰撞体前缘不越地形边缘

MoveWithSpeed(导航跟段驱动追击)原先无边缘夹紧,仅靠 nav 图,agent 按点定位
可让碰撞体前缘越出段/地形边缘约半个碰撞体宽。现加"悬崖"夹紧(WouldFallAhead,
按碰撞体前缘即时判定),贴地生效:追击时碰撞体边缘停在地形边缘、不越界,与 Pace
一致。只夹悬崖不夹墙(墙交寻路绕行,免误夹停);跨沟的 nav-link 跳/落不经
MoveWithSpeed(仅 IsMovingOnSegment 时写速度),故不影响跳跃/下落过沟。

WouldBlockAhead 拆为 WouldHitWallAhead / WouldFallAhead 复用;PlayMode 校验
拆分正确(前缘朝悬崖侧 WouldFall=True)、Pace 不回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
2026-07-10 15:48:58 +08:00
parent a4b9729de7
commit f1ca62f881
+28 -18
View File
@@ -226,33 +226,39 @@ namespace BaseGames.Enemies
return new Vector2(x, b.min.y); return new Vector2(x, b.min.y);
} }
private Collider2D GroundColliderOrSelf()
=> _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
/// <summary> /// <summary>
/// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘。 /// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘(墙 或 悬崖)
/// 用碰撞体前缘(bounds.max.x / min.x)判定,天然考虑碰撞体宽度—— /// 用碰撞体前缘(bounds.max.x / min.x)判定,天然考虑碰撞体宽度——
/// 保证"碰撞体边缘不越过地形边缘"。用当前请求方向即时射线,避免缓存朝向过期。 /// 保证"碰撞体边缘不越过地形边缘"。用当前请求方向即时射线,避免缓存朝向过期。
/// 供移动层夹紧与巡逻 Pace 翻向共用同一判定,避免"夹停了却不掉头"。 /// 供移动层夹紧与巡逻 Pace 翻向共用同一判定,避免"夹停了却不掉头"。
/// </summary> /// </summary>
public bool WouldBlockAhead(float dir) public bool WouldBlockAhead(float dir) => WouldHitWallAhead(dir) || WouldFallAhead(dir);
/// <summary>朝 <paramref name="dir"/> 前缘水平方向是否有墙。</summary>
public bool WouldHitWallAhead(float dir)
{ {
var col = _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>(); var col = GroundColliderOrSelf();
if (col == null) return false; if (col == null || _wallCheckDist <= 0f) return false;
Bounds b = col.bounds; Bounds b = col.bounds;
float sign = Mathf.Sign(dir); float sign = Mathf.Sign(dir);
float edgeX = sign >= 0f ? b.max.x : b.min.x; // 朝向侧碰撞体前缘 float edgeX = sign >= 0f ? b.max.x : b.min.x;
LayerMask wallLayer = (_wallMask.value != 0) ? _wallMask : _groundMask; LayerMask wallLayer = (_wallMask.value != 0) ? _wallMask : _groundMask;
return Physics2D.Raycast(new Vector2(edgeX, b.center.y), new Vector2(sign, 0f), _wallCheckDist, wallLayer);
}
// 墙:从前缘水平探;命中则前方有墙 /// <summary>朝 <paramref name="dir"/> 前缘外是否为悬崖(脚边地面缺失,会让碰撞体越出地形边缘)。</summary>
if (_wallCheckDist > 0f && public bool WouldFallAhead(float dir)
Physics2D.Raycast(new Vector2(edgeX, b.center.y), new Vector2(sign, 0f), _wallCheckDist, wallLayer)) {
return true; var col = GroundColliderOrSelf();
if (col == null || _ledgeCheckDownDist <= 0f) return false;
// 悬崖:从"前缘 + 前探偏移"处向下探地;无地面则前缘外是悬崖 Bounds b = col.bounds;
if (_ledgeCheckDownDist > 0f && float sign = Mathf.Sign(dir);
!Physics2D.Raycast(new Vector2(edgeX + sign * _ledgeCheckFwdOffset, b.min.y), float edgeX = sign >= 0f ? b.max.x : b.min.x;
Vector2.down, _ledgeCheckDownDist, _groundMask)) return !Physics2D.Raycast(new Vector2(edgeX + sign * _ledgeCheckFwdOffset, b.min.y),
return true; Vector2.down, _ledgeCheckDownDist, _groundMask);
return false;
} }
private void WallAndLedgeCheck() private void WallAndLedgeCheck()
@@ -416,7 +422,11 @@ namespace BaseGames.Enemies
public void MoveWithSpeed(float dir, float speed) public void MoveWithSpeed(float dir, float speed)
{ {
if (_isTurning) return; if (_isTurning) return;
UpdateFacing(dir); if (dir != 0f) UpdateFacing(dir);
// 地面怪追击/导航:前缘将越过地形边缘(悬崖)则不前进——碰撞体边缘停在地形边缘、不越界。
// 只夹"悬崖"不夹"墙"(墙交给寻路绕行,免得贴墙路径被误夹停);跨沟的 nav-link 跳/落
// 不经本方法(EnemyNavAgent 仅在 IsMovingOnSegment 时写 MoveSpeed),故不影响跳跃/下落过沟。
if (dir != 0f && IsGrounded && WouldFallAhead(dir)) dir = 0f;
var vel = _rb.velocity; var vel = _rb.velocity;
vel.x = dir * speed; vel.x = dir * speed;
_rb.velocity = vel; _rb.velocity = vel;