feat(enemy): Wander 巡逻改为当前段内随机 + 按身体宽度内缩两端
修复地面单位 Wander 巡逻"走一小段就停": - 随机点从全图(GetRandomPointOnGraph)改为限定在 agent 当前所站的 NavSegment 上, 不再跨越跳/落/拐角 NavLink,避免路径跨沟被崖边夹停或卡在过不去的 link。 - 段内随机 t 按"身体前缘安全余量(碰撞体半宽 + 悬崖检测前向偏移)"从两端各内缩, 避免随机点落在角色宽度站不住的崖沿(WouldFallAhead 夹停、IsFollowingAPath 不复位→卡死)。 - NavAgent.SetRandomDestinationOnCurrentSegment(endMargin):段内采样+内缩(段过短退化到中点) - EnemyMovement.EdgeSafeMargin:身体前缘安全余量 - IPathAgent.WalkToRandomOnSegment():EnemyNavAgent 传入 margin;飞行单位复用局部偏移;Null 返回 false Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -594,6 +594,32 @@ namespace PathBerserker2d
|
|||||||
return PathTo(goal);
|
return PathTo(goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [项目新增] 段内随机游走:目标点限制在 agent 当前所站的这一条 NavSegment(cluster) 上,
|
||||||
|
// 不会跨越任何 NavLink(跳/落/拐角),避免地面单位游走时被崖边夹停或卡在无法穿越的 link 处。
|
||||||
|
// 需要 agent 当前已映射到某条段上(currentMappedPosition 有效),否则返回 false。
|
||||||
|
// 注意:一条 cluster 是一条直线段;被拐角/斜坡拆成多段的平台只会在当前这一段内游走。
|
||||||
|
/// <summary>
|
||||||
|
/// Start pathfinding to a random position ON THE SAME segment the agent currently stands on.
|
||||||
|
/// Never crosses a NavLink, so a ground unit stays on its current platform segment.
|
||||||
|
/// <paramref name="endMargin"/> insets the random range from BOTH segment ends (world units),
|
||||||
|
/// so the goal is never so close to the edge that a bodied agent can't stand there
|
||||||
|
/// (pass the agent's half-width + ledge-check margin). If the segment is shorter than
|
||||||
|
/// 2*endMargin the goal degenerates to the segment midpoint.
|
||||||
|
/// Returns false if the agent is not currently mapped to a segment.
|
||||||
|
/// </summary>
|
||||||
|
public bool SetRandomDestinationOnCurrentSegment(float endMargin = 0f)
|
||||||
|
{
|
||||||
|
if (currentMappedPosition.IsInvalid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var cluster = currentMappedPosition.cluster;
|
||||||
|
float len = cluster.Length;
|
||||||
|
float m = Mathf.Clamp(endMargin, 0f, len * 0.5f); // 段太短 → 退化到中点
|
||||||
|
float t = UnityEngine.Random.Range(m, len - m);
|
||||||
|
Vector2 goal = cluster.owner.LocalToWorld.MultiplyPoint3x4(cluster.GetPositionAlongSegment(t));
|
||||||
|
return PathTo(goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If you implement link traversal yourself, call this to complete a link traversal.
|
/// If you implement link traversal yourself, call this to complete a link traversal.
|
||||||
|
|||||||
@@ -262,6 +262,21 @@ namespace BaseGames.Enemies
|
|||||||
Vector2.down, _ledgeCheckDownDist, _groundMask);
|
Vector2.down, _ledgeCheckDownDist, _groundMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身体中心到"能站住的平台边缘最近点"的水平安全余量 = 碰撞体水平半宽 + 悬崖检测前向偏移。
|
||||||
|
/// WouldFallAhead 在"碰撞体前缘 + _ledgeCheckFwdOffset"处探地,故身体中心必须离平台边缘至少此距离,
|
||||||
|
/// 否则前缘探出崖沿被夹停。供 nav 段内随机游走把随机点从段两端各内缩此值,避免随机到身体站不住的崖沿点。
|
||||||
|
/// </summary>
|
||||||
|
public float EdgeSafeMargin
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var col = GroundColliderOrSelf();
|
||||||
|
float halfWidth = col != null ? col.bounds.extents.x : 0f;
|
||||||
|
return halfWidth + Mathf.Max(0f, _ledgeCheckFwdOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void WallAndLedgeCheck()
|
private void WallAndLedgeCheck()
|
||||||
{
|
{
|
||||||
if (_wallCheckDist > 0f)
|
if (_wallCheckDist > 0f)
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ namespace BaseGames.Enemies
|
|||||||
/// <summary>移动到随机可到达点(巡逻 fallback)。</summary>
|
/// <summary>移动到随机可到达点(巡逻 fallback)。</summary>
|
||||||
bool WalkToRandom();
|
bool WalkToRandom();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 段内随机游走:随机点限制在 agent 当前所站的这一条导航段上,不跨越任何 NavLink(跳/落/拐角)。
|
||||||
|
/// 供 Wander 巡逻使用——地面单位只在当前平台段内来回,避免被崖边夹停或卡在过不去的 link 处。
|
||||||
|
/// 未映射到导航段(不在图上)时返回 false。
|
||||||
|
/// </summary>
|
||||||
|
bool WalkToRandomOnSegment();
|
||||||
|
|
||||||
// ── 连接段事件 ──────────────────────────────────────────────────
|
// ── 连接段事件 ──────────────────────────────────────────────────
|
||||||
/// <summary>开始穿越连接段时触发(传入连接段类型)。</summary>
|
/// <summary>开始穿越连接段时触发(传入连接段类型)。</summary>
|
||||||
event Action<NavLinkType> OnLinkStarted;
|
event Action<NavLinkType> OnLinkStarted;
|
||||||
@@ -91,6 +98,7 @@ namespace BaseGames.Enemies
|
|||||||
public Vector2 CurrentLinkEnd => Vector2.zero;
|
public Vector2 CurrentLinkEnd => Vector2.zero;
|
||||||
public bool CanReach(Vector2 _) => false;
|
public bool CanReach(Vector2 _) => false;
|
||||||
public bool WalkToRandom() => false;
|
public bool WalkToRandom() => false;
|
||||||
|
public bool WalkToRandomOnSegment() => false;
|
||||||
public event Action<NavLinkType> OnLinkStarted { add { } remove { } }
|
public event Action<NavLinkType> OnLinkStarted { add { } remove { } }
|
||||||
public event Action<NavLinkType> OnLinkCompleted{ add { } remove { } }
|
public event Action<NavLinkType> OnLinkCompleted{ add { } remove { } }
|
||||||
public event Action OnNavPathFailed { add { } remove { } }
|
public event Action OnNavPathFailed { add { } remove { } }
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ namespace BaseGames.Enemies
|
|||||||
switch (_patrolStrategy)
|
switch (_patrolStrategy)
|
||||||
{
|
{
|
||||||
case PatrolStrategy.Wander:
|
case PatrolStrategy.Wander:
|
||||||
if (_enemy.Nav != null && !_enemy.Nav.IsMoving) _enemy.Nav.WalkToRandom();
|
// 段内随机游走:随机点限制在当前所站的导航段上,不跨越 NavLink,
|
||||||
|
// 避免地面单位游走时被崖边夹停或卡在过不去的跳/落 link 处。
|
||||||
|
if (_enemy.Nav != null && !_enemy.Nav.IsMoving) _enemy.Nav.WalkToRandomOnSegment();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PatrolStrategy.Pace: // 撞墙/悬崖翻向的来回踱步(速度驱动,不依赖 nav)
|
case PatrolStrategy.Pace: // 撞墙/悬崖翻向的来回踱步(速度驱动,不依赖 nav)
|
||||||
|
|||||||
@@ -171,6 +171,11 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
|
|
||||||
public bool WalkToRandom() => _navAgent?.SetRandomDestination() ?? false;
|
public bool WalkToRandom() => _navAgent?.SetRandomDestination() ?? false;
|
||||||
|
|
||||||
|
// 段内随机游走:按身体前缘安全余量内缩段两端,避免随机点落在角色宽度站不住的崖沿(WouldFallAhead 夹停)。
|
||||||
|
public bool WalkToRandomOnSegment()
|
||||||
|
=> _navAgent?.SetRandomDestinationOnCurrentSegment(
|
||||||
|
_enemyMovement != null ? _enemyMovement.EdgeSafeMargin : 0f) ?? false;
|
||||||
|
|
||||||
public bool IsNearEdge()
|
public bool IsNearEdge()
|
||||||
{
|
{
|
||||||
var origin = (Vector2)transform.position;
|
var origin = (Vector2)transform.position;
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 飞行单位无"导航段"概念,其 WalkToRandom 本就是当前位置的有界局部偏移,
|
||||||
|
// 天然满足"就近游走"语义,直接复用。
|
||||||
|
public bool WalkToRandomOnSegment() => WalkToRandom();
|
||||||
|
|
||||||
// ── 内部移动逻辑 ───────────────────────────────────────────────
|
// ── 内部移动逻辑 ───────────────────────────────────────────────
|
||||||
private void UpdateChase()
|
private void UpdateChase()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user