寻路:A* 怎么少走冤枉路
Pathfinding & Navigation Theory
算法只认识图
NPC 要从屋子这头走到那头。我们看到的是一张关卡:墙、门、柱子、楼梯。寻路算法看到的却只有一张图 (Graph):一堆节点,和连接节点的边。
Red Blob Games 的 A* 教程特别强调这一点:边只表示「能从 A 到 B」,至于怎么迈步、要不要开门、走直线还是走曲线,算法一概不管。输入是图,输出是图上的一条路径,怎么把这条路径落到角色身上,是游戏自己的事。所以,图怎么建往往和算法选哪个一样重要。
图从哪来:格子、路点、导航网格
同一间带柱子的屋子,可以建成三种完全不同的图:
- 格子最容易实现,但节点非常多;
- 路点是手工或自动放的一组关键点,图很稀疏,但角色只能沿着连线走;
- 导航网格 (NavMesh) 把可行走区域建成一组多边形,节点常取多边形本身或者它们之间的公共边。节点少,路径又能在多边形内部任意取点。引擎一般从关卡的碰撞几何自动「烘焙」出导航网格。
一家人:BFS、Dijkstra、Greedy 和 A*
常见的寻路算法其实是同一个家族,区别在于「下一个先看哪个节点」:
| 算法 | 怎么挑下一个节点 | 什么时候用 |
|---|---|---|
| 广度优先 (BFS) | 一圈一圈往外扩 | 每条边的代价都一样 |
| Dijkstra(统一代价搜索) | 已经走过的路程最短的先看 | 边的代价不同,要最短路 |
| 贪心最佳优先 (Greedy Best-First) | 看起来离终点最近的先看 | 要快,可以不是最短 |
| A* | 最小的先看 | 单个终点的最短路,通常展开得更少 |
选型很简单:要找到所有点或多个点的路,用 BFS 或 Dijkstra;只要到一个终点,通常用 A*。
f = g + h
A* 给每个节点算一个分数:
- :从起点走到 已经花掉的代价,是实打实算出来的;
- :从 到终点还要花多少的估计,叫启发式 (Heuristic)。
每一步都展开 最小的节点。只要 从不高估真实的剩余距离(这样的启发式叫可采纳的 (Admissible)),A* 就保证找到最短路。
的大小决定了 A* 像谁: 越小,越接近 Dijkstra——只看走过的路,向四面八方均匀扩张; 越大,越接近 Greedy——只看离终点多远,冲得快,但不再保证最短。
动手:A* 到底省掉了多少
下面是一片没有障碍的空地。黄点是起点,鼠标是终点,橙线是最短路。滑块 k 控制启发式有多准: 到终点的直线距离。k = 0 时完全不用启发,就是 Dijkstra;k = 1 时 恰好等于真实距离。亮起来的格子,是在找到终点之前一定会被展开的格子。
也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。
分层:先订走廊,再细化
开放世界里,直接在一张覆盖全图的细网格上跑 A*,节点太多。常见的做法是分层:高层是一张很稀疏的图(房间、地标、扇区),先在上面订出一条「走廊级」的路线;进到具体区域以后,再在那一块的导航网格或格子上细化。
也可以用一张稀疏的路点图做战略层,导航网格做战术层。思路都一样:先在很少的节点上决定大方向,再在局部把细节补上。
全局路线和局部挤让
A* 和导航网格回答的是「大致怎么走」:绕开墙、找到门、上对楼层。可是一群角色同时走时,彼此之间的即时挤让,它们管不了——那是下一课局部避障的事。《游戏引擎架构》把寻路列为角色 AI 最底层的能力之一,局部避障则是叠在它上面的另一层。
A* 不管的事
Red Blob 明确列出了 A* 不负责的东西,每一项都要靠额外的系统:
- 多个角色协作移动、互相让路;
- 会移动的障碍、运行时变化的地图;
- 危险区域的评估;
- 编队;
- 转弯半径、物体的尺寸;
- 路径平滑,以及动画。
另外,A* 也不只用在寻路上。前面 GOAP 那一课里的规划器,就是把同一个 A* 搬到「世界状态」组成的图上,用来搜行动序列。
在游戏里怎么用
取舍与常见的坑
小结与练习
- 寻路算法只认识图,格子、路点、导航网格是三种建图方式,节点越少越快;
- BFS、Dijkstra、Greedy、A* 是一家人,A* 用 挑下一个节点;
- 启发式不高估,A* 就保证最短;越准,展开得越少;
- 大世界用分层:先在稀疏的图上订路线,再在局部细化;挤让交给局部避障。
练习:在画布的代码里加一面墙(比如一个矩形),让墙里的格子不参与。想一想:这时 还能用直线距离吗?亮起来的区域应该往哪个方向长?
寻路是在图上找代价可接受的路径:BFS / Dijkstra / A* 是同一家族;NavMesh、路点、格子是不同的图构建方式。分层(走廊↔房间、扇区↔网格)与「全局路径 + 局部避障」把大规模导航拆开。A* 本身不管编队、动态挤让与动画根运动。
图,而不是关卡图
Red Blob:算法只看见节点与边;边表示「可从 A 到 B」,不负责如何迈步、开门或曲线移动。图设计(格子 / 路点 / NavMesh 多边形)决定性能与路径观感。(Introduction to the A* Algorithm(笔记))
算法阶梯
| 算法 | 何时用 |
|---|---|
| BFS | 边权相等 |
| Dijkstra / UCS | 边权不同,要最短 |
| Greedy Best-First | 快,但不保证最短 |
| A* | ,可采纳启发时最优且通常更少扩展 |
变小 → 近 Dijkstra; 变大 → 近 Greedy。(同上)
GOAP 把同一 A* 用在「世界状态图」上——说明 A* 是通用图搜索,不绑定导航。(Three States and a Plan: The A.I. of F.E.A.R.(笔记))
导航网格 (NavMesh) 概念
把可行走区域建成多边形网格:节点常取多边形或边门户,边表示相邻可走。相对密网格:节点少、路径在多边形内可任意取点。引擎侧从碰撞几何烘焙 NavMesh(UE / Godot 实现页)。
分层与抽象
大世界常见做法(行业通识,与 Red Blob「先减少节点」一致):
- 高层图:房间、地标、扇区;
- 低层图:扇区内 NavMesh 或格子;
- 先订走廊级路径,再细化。
亦可用路点图做稀疏战略层,NavMesh 做战术层。
全局 vs 局部
A*/NavMesh 回答「大致怎么走」;密集人群中的即时挤让交给 局部避障(RVO/ORCA)。GEA 将寻路列为 AI 最底层之一。(GEA4 Vol II §18.2.3 Artificial Intelligence(摘录笔记))
A* 不包含的事
Red Blob 明确列出:协作移动、移动障碍、地图变更、危险区评估、编队、转弯半径、物体尺寸、路径平滑、动画等——都要额外系统。(Introduction to the A* Algorithm(笔记))
引擎对照
- UE:分瓦片 NavMesh、代价修改器、Nav Link、Detour Crowd——AI 感知、EQS 与导航。
- Godot:AStar*D 点图 vs NavigationServer 网格——Godot 导航与寻路。
出处
- Introduction to the A* Algorithm(笔记) Amit Patel / Red Blob Games, Introduction to A*
- www.redblobgames.com/pathfinding/a-star/introduction.html
- theory.stanford.edu/~amitp/GameProgramming
- Three States and a Plan: The A.I. of F.E.A.R.(笔记) Orkin(A* 用于规划状态空间)
- GEA4 Vol II §18.2.3 Artificial Intelligence(摘录笔记) GEA4(A* 作为底层)
- Navigation System UE Navigation(实现对照)
- Godot 官方文档(离线 HTML,stable) Godot Navigation