CD Code Digest Hub /GTA V

AI 行为任务、事件与流式导航

重点研究 Framework 可复用机制及其与游戏层的边界;并非逐个审计 GTA 的驾驶、战斗或人口行为。未运行路径搜索。入口:Summary。

8 分钟阅读 8 个章节 1 张图

1. 决策、任务和路径搜索分层#

Framework ai/task 组织行为状态及子任务,ai/event 表达事件,ai/navmesh 管理网格、搜索请求与路径线程;fwvehicleai 还有车辆路线相关数据。游戏层组合这些机制生成角色/车辆的具体动作。

AI Task 是持续的行为对象,Job 是一段可调度计算。把它们混用会误判可并行性和生命周期。

2. FSM 与层次任务树#

aiTask 提供 FSM 宏,明确区分 OnEnter、OnUpdate、OnExit。状态中可以设置子任务,等待 subtask finished 再转移;MakeAbortable 默认退出当前状态,并可通过默认恢复状态重新进入,也允许子类定制。

aiTaskManager 管理多个任务树和索引/优先级。由此可将总体行为和局部动作分解,避免单个巨型状态机直接承担全部逻辑。但中止语义很重要:动作可能已经持有动画、导航请求或对象引用,切换任务不是只替换指针。OnExit、取消请求与资源清理必须组成一致协议。

这个框架与现代通用 Behavior Tree 有共同的层次组织思想,但本次直接证据是 FSM、task tree 和任务管理接口,不能仅用“行为树”一词抹平其状态与中止机制。

3. AI 更新不是天然线程安全#

BeginTaskUpdates 设置静态 time step,EndTaskUpdates 清零;源码注释明确说明若要多线程更新 task,需要改变这种机制。它还通过断言约束 Begin/End 成对。这证明当前接口依赖共享更新上下文,不能把每个角色任务直接丢进通用 worker 后假定正确。

即使把时间改为参数,实体变更、动画请求、事件队列、随机数和资源所有权仍需逐项处理。新项目适合先并行纯查询或只读感知,产生命令后在确定阶段提交;行为状态机的变更保持明确所有权。

4. 导航搜索:层次数据、优先队列与可定制成本#

CSearchBase 将完成判断、相邻 polygon 可达性、代价/启发式和候选点评分做成虚接口。接口明确出现 A* heuristic;CPathSearchPriorityQueue 使用 binary heap。Heap 节点能表示普通 polygon 或 hierarchical node,保存 cost、已行进距离、位置与前向方向。

这支持在不同导航域/规则中复用搜索基础,但不能据此断言所有车辆/行人路径查询都采用完全相同算法。高层路径、局部 polygon 搜索、LOS、grid 和 floodfill 的目标与终止条件不同。

Heap 预分配最大节点数;节点移动时通过 Node** Handle 修正调用者持有的位置,文档要求 Handle 的存储持续有效。这是在避免重新查找的同时引入显式地址稳定性契约:搜索中断、重用 scratch 或移动结果对象都不能让 Handle 提前失效。

启发式是否 admissible、代价是否一致,取决于派生实现和导航规则。本文没有声称所有搜索都保证全局最短路径。

5. PathServer 的并发协议#

pathserverbase.h 要求所有访问请求集合的代码持 m_RequestsCriticalSectionToken;非 update thread 访问 LoadedMeshes 数组时还需单独的 loaded-mesh 临界区,因为主线程会随时修改数组。请求 semaphore、active/sleeping 标记、ForceAbortCurrentPathRequest 和 stall diagnostics 构成线程通信入口。

这是一种专用服务线程模型,不等于完全无锁队列。队列锁保护请求容器,不自动保护请求所指网格、游戏对象和回调目标。取消请求、停止线程和卸载导航块要以约定顺序协调。

框架头文件仍有游戏类前置声明和“移走非 RAGE 类”的 TODO,说明引擎/游戏边界并不彻底。移植时应抽象查询上下文、过滤器和结果接收接口,而不是直接复制游戏依赖。

6. 大世界导航:数据驻留就是搜索的一部分#

aiMeshStore::RequestAndEvict 接受多个 load regions;保留旧网格时使用 radius + meshRadius + 10,装载区域使用 radius + meshRadius,形成空间滞回。这个 10 是当前实现常量,不是通用最优距离。

需要的网格且未 requested/loading/loaded 时,以 STRFLAG_DONTDELETE 请求。卸载分支在 ref count 大于 1 时按该 Store 协议视为 defrag 进行中并推迟;否则先调用 PrepareToUnloadCB,再清保护标记并 StreamingRemove。这里的 ref count 解释是此局部实现的契约,不能推广到全引擎所有资源。

卸载附近的注释直接指出可能立即需要导航数据锁。这揭示关键权衡:即时释放有利内存,却可能让主线程等待正在搜索的线程。大量玩家兴趣点、高速移动或瞬移会把它放大为帧时问题。

aiNavMeshStore 的 CanPlaceAsynchronously 返回 false,注释说明它仍在 PlaceResource 内调用需要主线程的 Set;对应 SetResource 覆盖为空,避免重复 Set。这是通用 placement/发布流程的一个具体适配差异:即使原始 IO 异步,导航对象装配和接入仍有主线程工作。不能把“导航资源走 streaming”解释为整个导航加载过程都在后台完成。

7. 建议的新项目契约#

路径请求应携带起终点、导航版本、请求者世代、优先级和最大搜索工作量;结果提交时检查任务是否仍活跃、起点是否已显著移动、网格版本是否变化。以上是建议,不是声称现有结构完整包含这些字段。

数据可采用只读快照、epoch 或显式引用保护,避免搜索过程长期持有全局锁;动态障碍更新和网格卸载则在已知阶段提交。是否值得采用这些方法,取决于内存和同时搜索数量,不能用“无锁”替代成本评估。

风险场景关键指标建议处置方向
大量角色同时重规划队列年龄、搜索节点数、最老请求错峰、合并重复目标、优先级和工作预算
玩家高速穿越/瞬移导航缺块、同步 placement、卸载锁等待预取、多兴趣点、显式等待或降级行为
任务中止后结果到达陈旧结果计数、取消延迟请求世代和提交检查
高复杂度动态区heap 高水位、搜索上限层次路径、局部修复、可解释的失败码
直接并行化行为更新共享状态竞争与提交顺序只读查询并行,状态写入显式归属

以上实验均未执行。

8. 源码证据#

文件入口
task.h34–90 FSM/中止;97–115 静态时间步
taskmanager.h任务树/索引/优先级
searchbase.h完成、邻接、代价和启发式接口
priqueue.h19 binary heap;65–82 节点/预分配;128 起 Handle 生命周期
pathserverbase.h、pathserverthread.h49–52 锁契约;请求、信号、取消及线程状态
navmeshstore.h314 起 RequestAndEvict;435–478 请求/卸载;675 同步 placement

关联:大世界场景、Streaming、多线程、动画。