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:
@@ -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)
|
||||
|
||||
@@ -35,6 +35,10 @@ namespace BaseGames.Enemies.Navigation
|
||||
[Tooltip("两次重算最小间隔(s):留出跨帧路径计算完成的时间")]
|
||||
[SerializeField] private float _repathMinInterval = 0.25f;
|
||||
|
||||
[Header("目标点吸附")]
|
||||
[Tooltip("把巡逻/移动目标映射到最近可行走段时允许的最大偏离(m);超出视为无效目标(路点摆得离导航面太远)")]
|
||||
[SerializeField] private float _goalMapSearchRadius = 2f;
|
||||
|
||||
// 重寻路防抖状态
|
||||
private Vector2 _lastPathGoal;
|
||||
private bool _hasPathGoal;
|
||||
@@ -176,6 +180,16 @@ namespace BaseGames.Enemies.Navigation
|
||||
=> _navAgent?.SetRandomDestinationOnCurrentSegment(
|
||||
_enemyMovement != null ? _enemyMovement.EdgeSafeMargin : 0f) ?? false;
|
||||
|
||||
// 目标点吸附:把任意世界目标映射到最近可行走段、并按身体宽度(EdgeSafeMargin)从段两端内缩,
|
||||
// 得到"身体真正站得住"的点。身体宽度是本执行层独占的知识——意图层(AI/Locomotion)只递原始目标。
|
||||
// 映射不到任何段(目标离导航面太远)时返回 false,standable 回落为原始 target。
|
||||
public bool ResolveStandablePoint(Vector2 target, out Vector2 standable)
|
||||
{
|
||||
if (_navAgent == null) { standable = target; return false; }
|
||||
float margin = _enemyMovement != null ? _enemyMovement.EdgeSafeMargin : 0f;
|
||||
return _navAgent.TryGetStandablePointNear(target, _goalMapSearchRadius, margin, out standable);
|
||||
}
|
||||
|
||||
public bool IsNearEdge()
|
||||
{
|
||||
var origin = (Vector2)transform.position;
|
||||
|
||||
@@ -123,6 +123,13 @@ namespace BaseGames.Enemies.Navigation
|
||||
// 天然满足"就近游走"语义,直接复用。
|
||||
public bool WalkToRandomOnSegment() => WalkToRandom();
|
||||
|
||||
// 飞行单位直飞、无地面/崖沿/身体宽度约束——任意目标本就"站得住",原样返回即可。
|
||||
public bool ResolveStandablePoint(Vector2 target, out Vector2 standable)
|
||||
{
|
||||
standable = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── 内部移动逻辑 ───────────────────────────────────────────────
|
||||
private void UpdateChase()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user