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);
}
private Collider2D GroundColliderOrSelf()
=> _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
/// <summary>
/// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘。
/// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘(墙 或 悬崖)
/// 用碰撞体前缘(bounds.max.x / min.x)判定,天然考虑碰撞体宽度——
/// 保证"碰撞体边缘不越过地形边缘"。用当前请求方向即时射线,避免缓存朝向过期。
/// 供移动层夹紧与巡逻 Pace 翻向共用同一判定,避免"夹停了却不掉头"。
/// </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>();
if (col == null) return false;
var col = GroundColliderOrSelf();
if (col == null || _wallCheckDist <= 0f) return false;
Bounds b = col.bounds;
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;
return Physics2D.Raycast(new Vector2(edgeX, b.center.y), new Vector2(sign, 0f), _wallCheckDist, wallLayer);
}
// 墙:从前缘水平探;命中则前方有墙
if (_wallCheckDist > 0f &&
Physics2D.Raycast(new Vector2(edgeX, b.center.y), new Vector2(sign, 0f), _wallCheckDist, wallLayer))
return true;
// 悬崖:从"前缘 + 前探偏移"处向下探地;无地面则前缘外是悬崖
if (_ledgeCheckDownDist > 0f &&
!Physics2D.Raycast(new Vector2(edgeX + sign * _ledgeCheckFwdOffset, b.min.y),
Vector2.down, _ledgeCheckDownDist, _groundMask))
return true;
return false;
/// <summary>朝 <paramref name="dir"/> 前缘外是否为悬崖(脚边地面缺失,会让碰撞体越出地形边缘)。</summary>
public bool WouldFallAhead(float dir)
{
var col = GroundColliderOrSelf();
if (col == null || _ledgeCheckDownDist <= 0f) return false;
Bounds b = col.bounds;
float sign = Mathf.Sign(dir);
float edgeX = sign >= 0f ? b.max.x : b.min.x;
return !Physics2D.Raycast(new Vector2(edgeX + sign * _ledgeCheckFwdOffset, b.min.y),
Vector2.down, _ledgeCheckDownDist, _groundMask);
}
private void WallAndLedgeCheck()
@@ -416,7 +422,11 @@ namespace BaseGames.Enemies
public void MoveWithSpeed(float dir, float speed)
{
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;
vel.x = dir * speed;
_rb.velocity = vel;