fix(enemy): 目标点吸附改用距离自适应搜索,修复稍离地路点吸附失败
之前 TryGetStandablePointNear 用固定 2m 搜索半径(实为 ±1m AABB)+ agent 过滤映射, 导致摆放在离地面 >1m 的路点(如 wp y=3.16 而地面段 y=2.00)映射失败、误报"超出搜索半径"。 改为与 PB2d 自身 UpdatePath 映射目标完全一致:搜索距离=agent 到目标距离(+0.1), 用不带 agent 过滤的 TryMapPoint(就近段几何映射,可达性交给 allowCloseEnoughPath 寻路阶段)。 只要图上有段就一定吸附得到最近点。移除 EnemyNavAgent._goalMapSearchRadius 固定半径字段。 Play 实测:两个路点 ResolveStandablePoint 均 ok=True((-20.90,3.16)->(-20.90,2.00)), CanReach=True,敌人实际走到 x=-20.75(目标 -20.90)后停,无 EnemyLocomotion 报警。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -620,20 +620,24 @@ namespace PathBerserker2d
|
|||||||
return PathTo(goal);
|
return PathTo(goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [项目新增] 目标点吸附:把任意世界点映射到最近的、agent 容身得下的可行走段上,再按 endMargin
|
// [项目新增] 目标点吸附:把任意世界点映射到最近的可行走段,再按 endMargin 从段两端内缩,
|
||||||
// 从段两端内缩,得到"身体真正站得住"的世界点(避免落在角色宽度站不住的崖沿被移动层夹停)。
|
// 得到"身体真正站得住"的世界点(避免落在角色宽度站不住的崖沿被移动层夹停)。
|
||||||
// 与 SetRandomDestinationOnCurrentSegment 同源:endMargin 传 agent 半宽 + 落崖检测余量。
|
// 与 SetRandomDestinationOnCurrentSegment 同源:endMargin 传 agent 半宽 + 落崖检测余量。段短于
|
||||||
// 段短于 2*endMargin → 退化到中点。searchRadius 内映射不到任何段(目标离导航面太远)时返回 false。
|
// 2*endMargin → 退化到中点。映射方式与 PB2d 自身 UpdatePath 映射目标一致(见 UpdatePath):
|
||||||
|
// 搜索距离 = agent 到目标的距离(+余量)、用不带 agent 过滤的 TryMapPoint(就近段几何映射,可达性交给
|
||||||
|
// allowCloseEnoughPath 的寻路阶段),故只要图上有段就一定能吸附——不会像固定小半径那样漏掉稍离地的点。
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps <paramref name="worldTarget"/> to the nearest walkable segment the agent fits on,
|
/// Maps <paramref name="worldTarget"/> to the NEAREST walkable segment (same robustness as
|
||||||
|
/// PB2d's own goal mapping in UpdatePath — search distance scales with agent→target distance),
|
||||||
/// then insets by <paramref name="endMargin"/> from BOTH segment ends so the returned point is
|
/// then insets by <paramref name="endMargin"/> from BOTH segment ends so the returned point is
|
||||||
/// one a bodied agent can actually stand on (not clamped at a ledge). A segment shorter than
|
/// one a bodied agent can actually stand on (not clamped at a ledge). A segment shorter than
|
||||||
/// 2*endMargin degenerates to its midpoint. Returns false (and echoes worldTarget) if nothing
|
/// 2*endMargin degenerates to its midpoint. Returns false only if the graph has no segment at all.
|
||||||
/// maps within <paramref name="searchRadius"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryGetStandablePointNear(Vector2 worldTarget, float searchRadius, float endMargin, out Vector2 standable)
|
public bool TryGetStandablePointNear(Vector2 worldTarget, float endMargin, out Vector2 standable)
|
||||||
{
|
{
|
||||||
if (!PBWorld.TryMapPoint(worldTarget, searchRadius, this, out var ptr))
|
// 与 UpdatePath 映射目标同款:半径随距离放大,保证图上有段就映射得到最近点。
|
||||||
|
float searchDist = Vector2.Distance(Position, worldTarget) + 0.1f;
|
||||||
|
if (!PBWorld.TryMapPoint(worldTarget, searchDist, out var ptr))
|
||||||
{
|
{
|
||||||
standable = worldTarget;
|
standable = worldTarget;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
[Tooltip("两次重算最小间隔(s):留出跨帧路径计算完成的时间")]
|
[Tooltip("两次重算最小间隔(s):留出跨帧路径计算完成的时间")]
|
||||||
[SerializeField] private float _repathMinInterval = 0.25f;
|
[SerializeField] private float _repathMinInterval = 0.25f;
|
||||||
|
|
||||||
[Header("目标点吸附")]
|
|
||||||
[Tooltip("把巡逻/移动目标映射到最近可行走段时允许的最大偏离(m);超出视为无效目标(路点摆得离导航面太远)")]
|
|
||||||
[SerializeField] private float _goalMapSearchRadius = 2f;
|
|
||||||
|
|
||||||
// 重寻路防抖状态
|
// 重寻路防抖状态
|
||||||
private Vector2 _lastPathGoal;
|
private Vector2 _lastPathGoal;
|
||||||
private bool _hasPathGoal;
|
private bool _hasPathGoal;
|
||||||
@@ -182,12 +178,12 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
|
|
||||||
// 目标点吸附:把任意世界目标映射到最近可行走段、并按身体宽度(EdgeSafeMargin)从段两端内缩,
|
// 目标点吸附:把任意世界目标映射到最近可行走段、并按身体宽度(EdgeSafeMargin)从段两端内缩,
|
||||||
// 得到"身体真正站得住"的点。身体宽度是本执行层独占的知识——意图层(AI/Locomotion)只递原始目标。
|
// 得到"身体真正站得住"的点。身体宽度是本执行层独占的知识——意图层(AI/Locomotion)只递原始目标。
|
||||||
// 映射不到任何段(目标离导航面太远)时返回 false,standable 回落为原始 target。
|
// 底层用与 PB2d 自身目标映射同款的距离自适应搜索,只要图上有段就一定吸附得到;返回 false 仅表示图为空。
|
||||||
public bool ResolveStandablePoint(Vector2 target, out Vector2 standable)
|
public bool ResolveStandablePoint(Vector2 target, out Vector2 standable)
|
||||||
{
|
{
|
||||||
if (_navAgent == null) { standable = target; return false; }
|
if (_navAgent == null) { standable = target; return false; }
|
||||||
float margin = _enemyMovement != null ? _enemyMovement.EdgeSafeMargin : 0f;
|
float margin = _enemyMovement != null ? _enemyMovement.EdgeSafeMargin : 0f;
|
||||||
return _navAgent.TryGetStandablePointNear(target, _goalMapSearchRadius, margin, out standable);
|
return _navAgent.TryGetStandablePointNear(target, margin, out standable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsNearEdge()
|
public bool IsNearEdge()
|
||||||
|
|||||||
Reference in New Issue
Block a user