游戏 AI 感知与寻路:感知系统、A*/NavMesh 寻路与动态避障

深入游戏 AI 感知与寻路:感知系统(视野/听觉/记忆)、A* 寻路算法与启发式、NavMesh 导航网格与动态障碍、避障与寻路优化、行为树/状态机与寻路的衔接,以 Unity NavMesh、Godot Navigation、自研 A* 三重视角对照剖析。

「敌人会追我、会绕开障碍、会包抄」——这些看似智能的行为,底层是两套系统的配合:感知(AI 怎么知道玩家在哪)与寻路(AI 怎么走到玩家那)。很多开发者上来就套 A*,结果小地图能跑、大地图卡死,或敌人「看得见墙也绕不过去」。本文剥开 AI 感知与寻路外壳,聚焦四个核心模块:感知系统(AI 的「眼睛和耳朵」)、A 寻路原理与优化*、NavMesh 导航网格与动态障碍、避障与行为衔接,并用 Unity NavMesh、Godot Navigation 与自研 A* 三重视角对照。

建议先读 游戏 AI:行为树与寻路 建立 AI 决策层视角,本文深入其「感知 + 移动」一层。

1. 感知系统:AI 的「眼睛和耳朵」

1.1 感知模型

AI 感知 = 视野 + 听觉 + 记忆(三层)
  ├── 视野:扇形检测(角度 + 距离 + 遮挡)
  ├── 听觉:球形检测(声音源 + 音量衰减 + 距离)
  └── 记忆:感知结果存在「黑盒」里(目标位置、上次见到的时间)
flowchart LR
    A["玩家位置"] --> B["视野检测(扇形+遮挡)"]
    A --> C["听觉检测(球形+衰减)"]
    B --> D["感知记忆"]
    C --> D
    D --> E["行为决策"]

1.2 视野检测

// 视野:角度内 + 距离内 + 无遮挡
bool CanSee(Vector3 playerPos, Vector3 myPos, Vector3 forward, float fov, float range) {
    Vector3 toPlayer = playerPos - myPos;
    if (toPlayer.magnitude > range) return false;
    if (Vector3.Angle(forward, toPlayer) > fov / 2f) return false;
    if (Physics.Raycast(myPos, toPlayer, out hit, range)) {
        if (hit.collider.tag != "Player") return false;  // 被墙挡了
    }
    return true;
}

1.3 感知的代价控制

感知检测不能每帧对所有 AI 全做(会爆):
  ├── 频率控制:每 0.2~0.5s 检测一次(不是每帧)
  ├── 分级检测:近的每帧、远的低频
  ├── 遮挡用射线:预算控制在几条射线内
  └── 静态遮挡:可预计算遮挡关系,不用实时射线

记忆:感知是「视觉扇形 + 听觉球形 + 记忆黑盒」,关键在频率控制——别让 AI 的「眼睛」比玩家的 GPU 还贵。

2. A* 寻路原理与优化

2.1 A* 的核心

A* = 把地图抽象成图,找从起点到终点的最短路径
   ├── 节点(Node):格子/顶点
   ├── 代价(g):从起点到这的开销
   ├── 启发式(h):到这终点的估计开销
   └── f = g + h,每次扩展 f 最小的节点
A* 伪码:
  openList = [start]; cameFrom = {}; gScore[start] = 0
  while openList 非空:
    当前 = openList 中 f 最小的
    if 当前 == 终点: 回溯路径
    for 邻居 in 当前的邻居:
      g2 = gScore[当前] + 移动代价
      if g2 < gScore[邻居]:
        更新 gScore、记录 cameFrom、加入 openList

2.2 启发式与优化

优化做法效果
良好启发式曼哈顿/欧氏距离少扩展节点
二叉堆openList 用优先队列取最小 O(logn)
跳点搜索JPS(格子类地图)大幅减少节点
分层寻路上层粗路径 + 下层细走大地图
启发式是否准决定 A* 快慢:
  ├── h 低估 → 扩展多但路径准
  ├── h 高估(可接受)→ 扩展少但可能非最优
  └── 网格地图常用曼哈顿距离(四方向)/ 欧氏(任意方向)

记忆:A 的引擎是「f = g + h」*,工程优化在「堆 + 启发式 + 分层」,别小看 h 的估算——它决定你是扩展 100 个节点还是 10000 个。

3. NavMesh 导航网格与动态障碍

3.1 NavMesh 是什么

A* 在「格子图」上跑;NavMesh 把可行走区域变成「多边形网格」
  ├── 优点:贴合真实地形(斜坡、窄缝、台阶)
  ├── 生成:烘焙(离线)或运行时生成
  └── 寻路:在网格上做 A*,再平滑路径
NavMesh 生成流程(离线烘焙):
  场景几何 → 标记可行走区域 → 生成多边形网格 → 保存 .asset
运行时寻路:
  Agent 请求路径 → NavMesh 上 A* → 得到路径点 → Agent 沿路径移动

3.2 动态障碍处理

障碍不是固定的(门开合、柱子倒塌、玩家放置物):
  ├── 动态障碍:运行时把障碍区域「切出」NavMesh
  ├── 避障:Agent 之间互相避让(RVO/ORCA)
  └── 局部重寻:遇到新障碍 → 只重算局部路径
维度Unity NavMeshGodot Navigation
生成离线烘焙 / NavMeshSurface运行时 NavigationMesh
AgentNavMeshAgentNavigationAgent3D/2D
动态障碍NavMeshObstacle动态烘焙
避障内置需配置

记忆:NavMesh 是「给 A 一张贴地形的图」*。动态场景的关键是「障碍切图 + Agent 避让 + 局部重寻」,三者配合才能应付会变的世界。

4. 避障与寻路衔接

4.1 寻路 vs 避障

寻路(全局):从 A 到 B 的大方向(NavMesh/A*,低频)
避障(局部):眼前这步怎么走(RVO/ORCA,高频)
  ├── 全局寻路:每 0.5s 或到达节点时重算
  ├── 局部避障:每帧调整方向避免撞人/撞墙
  └── 融合:沿路径点走 + 每帧避障修正
Agent 移动每帧:
  1. 全局路径:当前目标节点(低频更新)
  2. 局部避障:修正方向(高频)
  3. 实际移动:朝修正后的方向走

4.2 与行为树衔接

寻路结果 → 行为树的「移动」动作:
  ├── 行为树决策「追玩家」→ 寻路到玩家位置 → 沿路径移动
  ├── 感知发现新目标 → 重新决策 → 重新寻路
  └── 寻路失败(无路可走)→ 行为树切「放弃/巡逻」

记忆:寻路解决「往哪走」,避障解决「怎么走」,行为树决定「要不要走」。三者是 AI 移动的三层,接错了(用避障做全局)就会「原地打转」。

5. 寻路性能优化

大地图 + 多敌人的寻路性能:
  ├── 寻路频率:不是每帧,是「到节点才重算」
  ├── 路径缓存:同一目的地多人共享缓存
  ├── 分层:上层导航(区域级)→ 下层细走
  └── 异步:寻路放后台线程,别卡主线程
优化场景收益
低频重寻移动中的敌人大
路径缓存群组同目标大
分层寻路大型开放世界大
异步寻路大量敌人防卡顿

心法:寻路性能的瓶颈在「次数」不在「单次」——把「每帧重算」改成「到点重算 + 缓存 + 分层」,百个敌人也能跑得动。

6. 最佳实践与总结

AI 感知与寻路决策清单:

  1. 感知控频:视野/听觉检测 0.2~0.5s 一次,别每帧全做。
  2. A 打好底*:堆 + 好启发式 + 分层,先快后准。
  3. NavMesh 贴地形:大地图别用格子,烘焙 NavMesh。
  4. 动态障碍三件套:切图 + 避让 + 局部重寻。
  5. 全局与局部分开:寻路定大方向、避障调小步,别混。

自研 AI 移动最小骨架推荐阅读顺序:感知检测 → A* 寻路 → 路径平滑 → 沿路径移动 → 局部避障。每完成一层,用一个「敌人追玩家绕障碍」的 demo 验证感知→寻路→移动的衔接。

AI 移动没有银弹:Unity NavMesh 现成、Godot Navigation 轻量、自研 A* 可控。但感知控频、全局/局部分离、路径缓存这三件事不分引擎必须做对——它们决定 100 个敌人时游戏卡不卡。

相关阅读:游戏 AI:行为树与寻路 讲解决策层;游戏性能剖析与优化 讲解寻路性能预算。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「game」更多文章

  1. 游戏关卡与资源流式加载:关卡切分、场景流式、资源优先级与加载屏
  2. 游戏存档与序列化:存档数据结构、版本迁移、校验与云存档
  3. 游戏 UI/HUD 系统:屏幕空间、数据绑定、生命周期与性能