AI寻路:NavLink 是并发协议,不是一段停顿时间
自动关联目录:AI寻路:NavLink 是并发协议,不是一段停顿时间
核心判断:门、跳跃点和窄通道上的 Smart NavLink 是多个 AI 竞争的共享资源。暂停几秒再继续只能解决单 AI 演出,不能解决并发占用、取消、关门和迟到回调。
一、业务冲突:门要有演出,寻路又不能被门卡死
AI 到门前需要停顿、开门、通过并按规则关门。路径查询还要按怪物类型设置 Area Cost 或禁行区;多人群聚时要局部避障。业务上看是一个“MoveTo”,引擎里却横跨 BT Task、UAITask_MoveTo、PathFollowing、Smart Link、门状态和 MovementComponent。
二、实际实现:已经下沉到 PathFollowing,但所有权分散
旧路径在切换 QueryFilter 时主动使当前路径失效,避免继续沿旧成本行走。新实现为每个 AIController 复制 QueryFilter,按 Area 设置成本,并把默认 PathFollowingComponent 替换为自定义组件。
BT Task 把门前停顿和通过时间写入 PathFollowing;组件缓存弱引用 NavLink 与目标点,调用 StopMovementKeepPathing,用两个 Timer 控制预停和结束,并在 Reset 中清理 Timer。NavLink 的可用性检查留在 C++ 布尔字段,没有进入脚本热路径,这一点正确。
sequenceDiagram
participant A as Agent MoveRequest
participant L as Link Arbiter
participant D as Door
A->>L: Acquire(LinkId, RequestId, Incarnation)
L-->>A: Lease
L->>D: first entrant opens door
A->>L: BeginTraverse(Lease)
alt complete
A->>L: Release(Lease)
else abort/repath/death
A->>L: Cancel(Lease)
end
L->>D: last entrant closes door三、值得保留:取消清理属于移动基础设施
TimerHandle 在 Reset 中统一清理、NavLink 使用弱引用、等待期间停止路径段更新,这些都比在行为树脚本里散落 Delay 更可靠。Area Cost 下沉到 QueryFilter,也让路径选择而非到达后碰撞来表达“可走但昂贵”。
四、结构性缺陷:同一个默认子对象无法同时拥有两套 PathFollowing
工程中另一种控制器用 UCrowdFollowingComponent 替换同名默认子对象以获得 Detour Crowd,而门逻辑又替换成自定义 UPathFollowingComponent。两者不是可叠加组件;若自定义类不继承 CrowdFollowing,门演出与 Crowd 避障只能二选一。
更具体的问题是 NavLink 只保存一个共享整数状态:第一个 AI 开门后,后续 AI 不再计数;任意一个 AI 完成就可能关门,哪怕还有别的 AI 正在通过。BT Task、AITask 和 PathFollowing 还各自保留停顿字段与默认值,部分回调没有实际绑定,谁拥有时序并不唯一。
五、目标设计:把 Link 变成带 Lease 的并发资源
移动请求生成单调 MoveRequestId,所有 Timer 和异步路径结果都捕获该 ID 与 Actor Incarnation。Link Arbiter 维护活跃 Lease 集合;0 -> 1 时开门,1 -> 0 时才允许关门。Abort、Repath、Stun、Death 和 Pool Return 必须走同一 Release 路径。Crowd 与门逻辑应合并到同一 PathFollowing 派生层,或明确用 CharacterMovement RVO 替代并记录取舍。
void OnLinkReached(MoveContext m, LinkHandle link)
{
LinkLease lease = Links.TryAcquire(link, m.agent, m.requestId);
if (!lease) return Repath();
OwnedLease = lease;
Schedule(m.prePause, [this, token = m.Token()] {
if (!IsCurrent(token) || !OwnedLease.IsValid()) return;
BeginCustomTraversal(OwnedLease);
});
}
void AbortMove()
{
CancelOwnedTimers();
Links.Release(OwnedLease);
OwnedLease = {};
}六、决策规则:移动请求、路径和 Link 各有生命周期
| 对象 | 唯一标识 | 必须取消的时机 |
|---|---|---|
| MoveRequest | RequestId + Actor Incarnation | 新 MoveTo、Stun、Death、回池 |
| Path | PathId + NavRevision | 动态门变化、Filter 变化、Repath |
| Link Lease | LinkId + LeaseId | 完成、Abort、掉线、对象销毁 |
| Door State | Door Revision | 由 Lease 集合驱动,不由单个 AI 写死 |
| Crowd Agent | Crowd RegistrationId | Controller/Pawn 解绑、降级模拟 |
| Timer/Callback | Captured RequestId | 回调执行前校验当前请求 |
这段实现已经把 Smart Link 时序放到了正确的 UE 层级;真正的升级点是从“单 AI 的定时器”提升为“多 AI 的并发协议”,并解决 CrowdFollowing 与自定义 PathFollowing 的继承冲突。