将"角色导航宽度"收口到导航执行层单一入口,意图层(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>
117 lines
5.6 KiB
C#
117 lines
5.6 KiB
C#
using System;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
// ── 连接段类型(映射 PB2d LinkTypeName 字符串)───────────────────────
|
||
public enum NavLinkType
|
||
{
|
||
None,
|
||
Segment, // 普通地面/平台段
|
||
Jump,
|
||
Fall,
|
||
Corner,
|
||
Climb,
|
||
Elevator,
|
||
Teleport,
|
||
Custom
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导航代理抽象接口(架构 07_EnemyModule §5)。
|
||
/// EnemyBase 和 BD Task 只依赖此接口,不依赖具体导航库。
|
||
/// 实现:EnemyNavAgent(PathBerserker2d);测试用 NullPathAgent。
|
||
/// </summary>
|
||
public interface IPathAgent
|
||
{
|
||
// ── 核心移动 ────────────────────────────────────────────────────
|
||
/// <summary>请求移动到世界坐标 target。</summary>
|
||
void RequestMoveTo(Vector2 target);
|
||
|
||
/// <summary>立即停止导航(清除路径)。</summary>
|
||
void StopNavigation();
|
||
|
||
/// <summary>是否已到达目标(距离 ≤ stoppingDistance)。</summary>
|
||
bool IsAtDestination();
|
||
|
||
/// <summary>运行时覆盖移动速度。</summary>
|
||
void SetSpeed(float speed);
|
||
|
||
/// <summary>当前帧是否在移动(速度 > 0.01 且有有效路径)。</summary>
|
||
bool IsMoving { get; }
|
||
|
||
/// <summary>是否接近平台边缘(脚下或前方无地面时为 true)。</summary>
|
||
bool IsNearEdge();
|
||
|
||
// ── 连接段感知(NavLink 平台跳跃/爬梯/传送)─────────────────────
|
||
/// <summary>当前是否正在穿越连接段(跳跃/下落/爬梯等)。</summary>
|
||
bool IsOnLink { get; }
|
||
|
||
/// <summary>当前正在穿越的连接段类型;不在连接段时为 None。</summary>
|
||
NavLinkType CurrentLinkType { get; }
|
||
|
||
/// <summary>当前连接段的起始世界坐标;不在连接段时为 Vector2.zero。</summary>
|
||
Vector2 CurrentLinkStart { get; }
|
||
|
||
/// <summary>当前连接段的目标世界坐标;不在连接段时为 Vector2.zero。</summary>
|
||
Vector2 CurrentLinkEnd { get; }
|
||
|
||
/// <summary>检查目标是否可以从当前位置导航到达(同步轻量查询)。</summary>
|
||
bool CanReach(Vector2 target);
|
||
|
||
/// <summary>移动到随机可到达点(巡逻 fallback)。</summary>
|
||
bool WalkToRandom();
|
||
|
||
/// <summary>
|
||
/// 段内随机游走:随机点限制在 agent 当前所站的这一条导航段上,不跨越任何 NavLink(跳/落/拐角)。
|
||
/// 供 Wander 巡逻使用——地面单位只在当前平台段内来回,避免被崖边夹停或卡在过不去的 link 处。
|
||
/// 未映射到导航段(不在图上)时返回 false。
|
||
/// </summary>
|
||
bool WalkToRandomOnSegment();
|
||
|
||
/// <summary>
|
||
/// 目标点吸附:把任意世界 <paramref name="target"/> 映射到最近可行走段、并按敌人身体宽度从段两端内缩,
|
||
/// 得到"身体真正站得住"的点 <paramref name="standable"/>(避免落在角色宽度站不住的崖沿被移动层夹停)。
|
||
/// 身体宽度只属于导航执行层——意图层(AI/Locomotion)递原始目标即可,无需自行计算宽度。
|
||
/// target 离导航面太远(超出搜索半径)时返回 false,standable 回落为原始 target。
|
||
/// </summary>
|
||
bool ResolveStandablePoint(Vector2 target, out Vector2 standable);
|
||
|
||
// ── 连接段事件 ──────────────────────────────────────────────────
|
||
/// <summary>开始穿越连接段时触发(传入连接段类型)。</summary>
|
||
event Action<NavLinkType> OnLinkStarted;
|
||
|
||
/// <summary>连接段穿越完成时触发。</summary>
|
||
event Action<NavLinkType> OnLinkCompleted;
|
||
|
||
/// <summary>路径寻路失败事件(目标不可达时触发)。</summary>
|
||
event Action OnNavPathFailed;
|
||
|
||
/// <summary>到达目标事件(替代轮询 IsAtDestination)。</summary>
|
||
event Action OnGoalReached;
|
||
}
|
||
|
||
// ── 无导航 / 测试用空实现 ─────────────────────────────────────────────
|
||
public sealed class NullPathAgent : IPathAgent
|
||
{
|
||
public void RequestMoveTo(Vector2 _) { }
|
||
public void StopNavigation() { }
|
||
public bool IsAtDestination() => true;
|
||
public void SetSpeed(float _) { }
|
||
public bool IsMoving => false;
|
||
public bool IsNearEdge() => false;
|
||
public bool IsOnLink => false;
|
||
public NavLinkType CurrentLinkType => NavLinkType.None;
|
||
public Vector2 CurrentLinkStart => Vector2.zero;
|
||
public Vector2 CurrentLinkEnd => Vector2.zero;
|
||
public bool CanReach(Vector2 _) => false;
|
||
public bool WalkToRandom() => false;
|
||
public bool WalkToRandomOnSegment() => false;
|
||
public bool ResolveStandablePoint(Vector2 target, out Vector2 standable) { standable = target; return false; }
|
||
public event Action<NavLinkType> OnLinkStarted { add { } remove { } }
|
||
public event Action<NavLinkType> OnLinkCompleted{ add { } remove { } }
|
||
public event Action OnNavPathFailed { add { } remove { } }
|
||
public event Action OnGoalReached { add { } remove { } }
|
||
}
|
||
}
|