feat(enemy): 目标点吸附统一处理导航宽度,Waypoint 不再卡崖边

将"角色导航宽度"收口到导航执行层单一入口,意图层(Locomotion/AI)只递原始目标、不再感知身体尺寸:

- NavAgent 新增 TryGetStandablePointNear:把世界点映射到最近可行走段并按 endMargin 内缩两端(与 SetRandomDestinationOnCurrentSegment 同源)
- EnemyNavAgent 新增 ResolveStandablePoint,以 EnemyMovement.EdgeSafeMargin(半宽+落崖余量)为唯一宽度真源;加 _goalMapSearchRadius
- IPathAgent 暴露 ResolveStandablePoint;FlyingDirectNavigator 原样返回(飞行无崖沿约束),NullPathAgent 空实现
- EnemyLocomotion Waypoint 分支改用吸附后的可达点做移动与到达判定,修复路点摆在空中/崖外时永不到达而卡死;吸附失败显式报错暴露误配,不静默兜底

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 15:52:18 +08:00
co-authored by Claude Opus 4.8
parent 7f7d237915
commit 132cd24908
5 changed files with 79 additions and 2 deletions
@@ -38,6 +38,8 @@ namespace BaseGames.Enemies
private int _wpIndex = -1;
private int _wpDir = 1;
private bool _warnedNoWaypoints;
// 当前路点吸附后的"站得住"目标(宽度由 Nav 层解析,本层不感知身体尺寸)
private Vector2 _wpResolvedGoal;
// Wander 到点停顿状态
private bool _wanderPausing;
private float _wanderPauseTimer;
@@ -193,16 +195,34 @@ namespace BaseGames.Enemies
}
// 仅首次或"已到达当前路点"时推进并寻路——不依赖 IsMoving
// PathBerserker2d 寻路跨帧异步,IsMoving 在寻路计算期间为 false,会误触发每帧推进)。
// 到达判定对"吸附后的可达点"做,而非原始路点:原始路点可能摆在空中/崖外,
// 与之的距离永远进不了到达半径 → 卡死;吸附点是身体站得住的可达点。
if (_wpIndex < 0 ||
Vector2.Distance(_enemy.transform.position, _waypoints[_wpIndex].position) <= _waypointArriveRadius)
Vector2.Distance(_enemy.transform.position, _wpResolvedGoal) <= _waypointArriveRadius)
{
AdvanceWaypoint();
_enemy.MoveTo(_waypoints[_wpIndex].position);
SetWaypointGoal();
}
break;
}
}
/// <summary>
/// 解析当前路点为"身体站得住的可达点"并出发。宽度处理下沉给 Nav 层(<see cref="IPathAgent.ResolveStandablePoint"/>),
/// 本层只递原始路点坐标、不感知身体尺寸。吸附失败=路点摆得离导航面太远,显式报错暴露根因,不静默兜底。
/// </summary>
private void SetWaypointGoal()
{
Vector2 raw = _waypoints[_wpIndex].position;
if (_enemy.Nav == null || !_enemy.Nav.ResolveStandablePoint(raw, out _wpResolvedGoal))
{
_wpResolvedGoal = raw;
Debug.LogWarning($"[EnemyLocomotion] 路点 '{_waypoints[_wpIndex].name}'(index {_wpIndex}) 无法吸附到任何可行走导航段" +
"(超出搜索半径)。请检查该路点是否摆放在贴近地面/平台的可行走处。", this);
}
_enemy.MoveTo(_wpResolvedGoal);
}
private void AdvanceWaypoint()
{
if (_pingPong)