JavaScript 实现 A* 寻路算法详解与代码示例
本文详细解析 A* 寻路算法在 JavaScript 中的实现过程。通过开启列表与关闭列表的管理,结合 F=G+H 估价函数计算最优路径,并处理障碍物与斜向移动逻辑。文章提供完整的 JS 代码示例,适用于游戏地图寻路等场景,帮助开发者掌握核心算法原理。
JavaScript 实现 A* 寻路算法详解与代码示例
本文通过具体实例,介绍如何使用 JavaScript 实现 A* 寻路算法,供读者参考学习。
近期在处理百度前端技术学院的相关题目时,遇到了寻路功能的需求,因此查阅了相关技术博客进行深入研究。
阅读了 Create Chen 撰写的《理解 A* 寻路算法具体过程》一文后,我对 A* 算法的核心原理有了清晰的认识。该作者的讲解通俗易懂,配图精良,可见投入了大量心血。若由我来撰写,恐怕难以达到如此水准。
然而,原文提供的源码并非 JavaScript 版本,导致我无法直接复用。鉴于此,我决定重新撰写一篇面向 JavaScript 学习者的教程。前文的概念描述我将沿用原作者的表述,因为我的文字表达能力尚显不足,借用原文能更准确地传达知识。
简易地图构建
简易地图构建

如图所示的简易地图中,绿色方块代表起点(标记为 A),中间蓝色区域为障碍物,红色方块代表终点(标记为 B)。为了便于使用二维数组表示地图结构,我们将整个地图划分为若干个小方块网格。
二维数组在游戏开发中应用广泛,例如贪吃蛇和俄罗斯方块的基本原理均涉及方块的移动。而在大型游戏中,地图则是通过在这些小方块上铺设不同的“地貌”纹理来构建的。
寻路执行步骤
寻路执行步骤
1. 从起点 A 开始,将其作为待处理节点存入“开启列表”(Open List),该列表用于存放待检查的方格。 2. 查找起点 A 周围所有可通行的方格,将它们加入“开启列表”,并将它们的“父方格”设置为 A。 3. 将起点 A 从“开启列表”中移除,并加入“关闭列表”(Closed List)。关闭列表用于存储已检查且无需再次处理的方格。

图中带有浅绿色边框的方块表示已加入“开启列表”等待检查;带有淡蓝色边框的起点 A 表示已移入“关闭列表”,无需再执行检查操作。
接下来,从“开启列表”中筛选出相对最优的方格。判断标准是通过公式 F = G + H 计算得出的数值。
F = G + H 其中,G 表示从起点 A 移动到网格上指定方格的实际移动耗费(允许沿斜方向移动)。 H 表示从指定方格移动到终点 B 的预计耗费(此处设定仅允许上下左右移动,不计算斜向)。

假设横向或纵向移动一个格子的耗费为 10,为了便于计算,沿斜方向移动一个格子的耗费设为 14。为了直观展示 F、G、H 的运算过程,图中方块左上角的数字代表 F 值,左下角代表 G 值,右下角代表 H 值。请核对计算结果是否与您的预期一致?
从“开启列表”中选取 F 值最低的方格 C(即绿色起始方块 A 右侧的方块),并对其进行以下处理:
4. 将该方格从“开启列表”中移除,并加入“关闭列表”。 5. 检查该方格周围所有相邻且可到达的方格(忽略障碍物和已在“关闭列表”中的方格)。若这些方格尚未在“开启列表”中,则将其加入,计算其 G、H 和 F 值,并设置其“父方格”为当前选中的方格 C。 6. 若某个相邻方格 D 已在“开启列表”中,则检查经由当前方格 C 到达 D 的路径是否更优(即 G 值是否更低)。若新路径的 G 值更低,则将 D 的“父方格”更新为 C,并重新计算 D 的 F 值和 G 值(H 值不变,因为它是基于目标点计算的固定值)。若新路径 G 值较高,说明经由 C 到达 D 并非明智选择,此时不做任何操作。

如图所示,我们选中了 F 值最小的方格 C,将其从“开启列表”移除并加入“关闭列表”。C 的右侧及上下方均为墙壁,故不予考虑;左侧为起始方块,已存在于“关闭列表”中,亦不考虑。因此,C 周围的候选方格仅剩 4 个。以 C 下方的格子为例,其当前 G 值为 14,若经由 C 到达该格子,G 值将变为 10 + 10 = 20,高于现有的 14,因此不做任何更改。
随后,我们继续从“开启列表”中查找 F 值最小的方格。此时发现 C 上方和下方的方格 F 值同为 54。在这种情况下,任选其一即可,例如我们选择 C 下方的方格 D。

D 的右侧及右上方均为墙壁,故不考虑。为何右下角的方格未被加入“开启列表”?这是因为若 C 下方的方格不可通行,要到达 C 右下角的方格需从“方块的角”穿过。程序中通常设置是否允许这种走法(本示例中不允许斜穿障碍物角)。

重复上述过程:从“开启列表”中找出 F 值最小的方格,将其移出并加入“关闭列表”,再查找其周围可到达的方格,如此循环迭代。
寻路何时停止?当“开启列表”中出现目标终点方块时,说明路径已成功找到。
路径回溯方法
路径回溯方法

如上图所示, 除了起始方块, 每一个曾经或者现在还在 "开启列表" 里的方块, 它都有一个 "父方块", 通过 "父方块" 可以索引到最初的 "起始方块", 这就是路径.
算法流程抽象
算法流程抽象
将起始格添加到“开启列表”。
do { 寻找开启列表中 F 值最低的格子,称其为当前格。 将其切换到关闭列表。 对当前格相邻的 8 个方向中的每一个进行判断: if (它不可通过 || 已经在 "关闭列表" 中) { 什么也不做。 } if (它不在开启列表中) { 把它添加进 "开启列表", 把当前格作为这一格的父节点, 计算这一格的 FGH。 } if (它已经在开启列表中) { if (用G值为参考检查新的路径是否更好, 更低的G值意味着更好的路径) { 把这一格的父节点改成当前格, 并且重新计算这一格的 GF 值。 } } } while( 目标格已经在 "开启列表", 这时候路径被找到 )
若开启列表已空仍未找到目标,则说明不存在可行路径。
最后,从目标格开始,沿着每一格的父节点移动,直到回到起始格,即为所求路径。
JavaScript 代码实现如下
JavaScript 代码实现如下:
//其中的MAP.arr是二维数组
function searchRoad(start_x,start_y,end_x,end_y){
var openList=[], //开启列表
closeList=[], //关闭列表
result=[], //结果数组
result_index; //结果数组在开启列表中的序号
openList.push({x:start_x,y:start_y,G:0});//把当前点加入到开启列表中,并且G是0
do{
var currentPoint = openList.pop();
closeList.push(currentPoint);
var surroundPoint=SurroundPoint(currentPoint);
for(var i in surroundPoint) {
var item = surroundPoint[i]; //获得周围的八个点
if (
item.x>=0 && //判断是否在地图上
item.y>=0 &&
item.x<MAP.rows &&
item.y<MAP.cols &&
MAP.arr[item.x][item.y] != 1 && //判断是否是障碍物
!existList(item, closeList) && //判断是否在关闭列表中
MAP.arr[item.x][currentPoint.y]!=1 && //判断之间是否有障碍物,如果有障碍物是过不去的
MAP.arr[currentPoint.x][item.y]!=1) {
//g 到父节点的位置
//如果是上下左右位置的则g等于10,斜对角的就是14
var g = currentPoint.G + ((currentPoint.x - item.x) * (currentPoint.y - item.y) == 0 ? 10 : 14);
if (!existList(item, openList)) { //如果不在开启列表中
//计算H,通过水平和垂直距离进行确定
item['H'] = Math.abs(end_x - item.x) * 10 + Math.abs(end_y - item.y) * 10;
item['G'] = g;
item['F'] = item.H + item.G;
item['parent'] = currentPoint;
openList.push(item);
}
else { //存在在开启列表中,比较目前的g值和之前的g的大小
var index = existList(item, openList);
//如果当前点的g更小
if (g < openList[index].G) {
openList[index].parent = currentPoint;
openList[index].G = g;
openList[index].F=g+openList[index].H;
}
}
}
}
//如果开启列表空了,没有通路,结果为空
if(openList.length==0) {
break;
}
openList.sort(sortF);//这一步是为了循环回去的时候,找出 F 值最小的, 将它从 "开启列表" 中移掉
}while(!(result_index=existList({x:end_x,y:end_y},openList)));
//判断结果列表是否为空
if(!result_index) {
result=[];
}
else {
var currentObj=openList[result_index];
do{
//把路劲节点添加到result当中
result.unshift({
x:currentObj.x,
y:currentObj.y
});
currentObj=currentObj.parent;
}while (currentObj.x!=start_x || currentObj.y!=start_y);
}
return result;
}
//用F值对数组排序
function sortF(a,b){
return b.F- a.F;
}
//获取周围八个点的值
function SurroundPoint(curPoint){
var x=curPoint.x,y=curPoint.y;
return [
{x:x-1,y:y-1},
{x:x,y:y-1},
{x:x+1,y:y-1},
{x:x+1,y:y},
{x:x+1,y:y+1},
{x:x,y:y+1},
{x:x-1,y:y+1},
{x:x-1,y:y}
]
}
//判断点是否存在在列表中,是的话返回的是序列号
function existList(point,list) {
for(var i in list) {
if(point.x==list[i].x && point.y==list[i].y) {
return i;
}
}
return false;
}
更多关于 JavaScript 相关内容感兴趣的读者可查看本站专题:《JavaScript数学运算用法总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript数组操作技巧总结》、《JavaScript排序算法总结》、《JavaScript遍历算法与技巧总结》、《JavaScript查找算法技巧总结》及《JavaScript错误与调试技巧总结》。
希望本文所述的 JavaScript 程序设计内容能对大家有所帮助。
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
