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:
2026-07-22 12:45:01 +08:00
co-authored by Claude Opus 4.8
parent 46c9718793
commit 31b77fa473
6 changed files with 61 additions and 1 deletions
@@ -594,6 +594,32 @@ namespace PathBerserker2d
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>
/// If you implement link traversal yourself, call this to complete a link traversal.