点赞 + 关注 + 收藏 = 学会了
本文简介
在 Web 应用和游戏中,路径规划是一个核心功能,无论是在地图导航、策略游戏的单位移动,还是虚拟现实中的导航辅助,都离不开高效的路径查找算法。
在一个 Web 项目中,路径规划通常是放在后端做的。但其实前端也可以实现这个功能。在前端实现还可以降低服务器压力。
PathFinding.js
作为一个基于网格的纯前端路径规划工具,其轻量级、易于集成和强大的功能,是「纯前端」实现路径规划的一个不错选择。
本文将探讨 PathFinding.js
的基础使用方法。
在学习 PathFinding.js
之前,可以先体验一下它的能力。
-
PathFinding.js
仓库:github.com/qiao/PathFi...
安装
使用 npm 的方式将 PathFinding.js
安装到项目中
bash
npm install pathfinding
基础用法
我用 React
+ PathFinding.js
举例说明。
使用 PathFinding.js
实现路径规划,需要做以下几步:
- 引入
PathFinding.js
。 - 创建网格地图(有地图才有路嘛)。
- 设置路障(哪里不可以走)。
- 设置起点和终点,调用路径规划算法,将可行路径规划出来。
一步步来,先看看这些功能要调哪些API。
1、引入 PathFinding.js
在使用 PathFinding.js
之前需要将其引入。
js
import PF from 'pathfinding'
2、创建网格 (Grid)
PF.Grid
是 PathFinding.js
库中的一个核心组件,它用于创建和表示一个二维网格,这个网格是路径查找算法的基础。
js
const grid = new PF.Grid(width, height)
- width 和 height 是网格的宽和高。
- 每个网格点初始化为通行 (0)。
举个例子。
js
// 创建一个 10x10 的网格
const grid = new PF.Grid(10, 10)
console.log(grid)
在控制台可以看到这个地图其实就是一个二维数组,长和宽都是10个元素。
3、设置路障
通过 grid.setWalkableAt()
方法可以设置指定节点的状态。
通过 grid.isWalkableAt()
可以检查指定节点是否可通行。
js
grid.setWalkableAt(x, y, walkable)
grid.isWalkableAt(x, y)
setWalkableAt(x, y, walkable)
:设置某点是否可通行。isWalkableAt(x, y)
:检查某点是否可通行。
举个例子。
js
// 设置 (5, 5) 为不可通行
grid.setWalkableAt(5, 5, false)
// 检查 (5, 5) 是否可通行
console.log(grid.isWalkableAt(5, 5)) // false
4、查找路径
PathFinding.js
提供了很多种查找路径的算法,我们先试试 PF.AStarFinder
算法。
查找路径具体用法如下:
js
const finder = new PF.AStarFinder(options);
const path = finder.findPath(startX, startY, endX, endY, grid);
options
:配置寻路算法的选项,如是否允许对角移动。findPath(startX, startY, endX, endY, grid)
:寻找从起点到终点的路径。startX
和startY
:起点坐标。endX
和endY
:终点坐标。grid
:网格地图。
- 返回值是路径点数组
[ [x1, y1], [x2, y2], ... ]
。
举个例子。
js
const finder = new PF.AStarFinder();
const path = finder.findPath(0, 0, 9, 9, grid.clone());
console.log(path);
除了 AStarFinder
,PathFinding.js
提供了多种算法:
BreadthFirstFinder
DijkstraFinder
BiAStarFinder
BiBreadthFirstFinder
BiDijkstraFinder
IDAStarFinder
JumpPointFinder
用法类似,只需替换 AStarFinder
即可。
在 React
里使用
js
// 导入 React 和 useState 钩子
import React, { useState } from 'react';
// 导入路径寻找库
import PF from 'pathfinding';
// 定义路径寻找应用组件
const PathFindingApp = () => {
// 初始化状态:网格、路径、起点和终点
const [grid, setGrid] = useState(new PF.Grid(10, 10)); // 创建 10x10 的网格
console.log(grid)
const [path, setPath] = useState([]); // 存储找到的路径
const [start, setStart] = useState([0, 0]); // 起点坐标
const [end, setEnd] = useState([9, 9]); // 终点坐标
// 切换网格单元的可行走状态
const toggleWalkable = (x, y) => {
const newGrid = grid.clone(); // 克隆当前网格
newGrid.setWalkableAt(x, y, !newGrid.isWalkableAt(x, y)); // 切换可行走状态
setGrid(newGrid); // 更新网格状态
};
// 寻找路径的函数
const findPath = () => {
const finder = new PF.AStarFinder(); // 创建 A* 寻路算法实例
const newGrid = grid.clone(); // 克隆当前网格
const newPath = finder.findPath(start[0], start[1], end[0], end[1], newGrid); // 寻找路径
setPath(newPath); // 更新路径状态
};
return (
<div>
{/* <h1>PathFinding.js in React</h1> */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(10, 30px)' }}>
{/* 渲染网格 */}
{[...Array(10)].map((_, y) =>
[...Array(10)].map((_, x) => {
const isWalkable = grid.isWalkableAt(x, y); // 判断当前单元是否可行走
const isPath = path.some(([px, py]) => px === x && py === y); // 判断当前单元是否在路径上
const isStart = x === start[0] && y === start[1]; // 判断当前单元是否为起点
const isEnd = x === end[0] && y === end[1]; // 判断当前单元是否为终点
return (
<div
key={`${x}-${y}`}
onClick={() => toggleWalkable(x, y)} // 点击切换可行走状态
style={{
width: 30,
height: 30,
backgroundColor: isStart
? 'green' // 起点为绿色
: isEnd
? 'red' // 终点为红色
: isPath
? 'yellow' // 路径为黄色
: isWalkable
? 'white' // 可行走单元为白色
: 'black', // 不可行走单元为黑色
border: '1px solid gray',
cursor: 'pointer',
}}
/>
);
})
)}
</div>
<button onClick={findPath}>Find Path</button> {/* 寻找路径按钮 */}
</div>
);
};
// 导出组件
export default PathFindingApp;
结合代码注释和前面的 API 讲解,应该看得懂我上面写的这份代码。
用了 100 个 div 创建了一个网格地图,绿色点是起点,红色点是终点,黑点是不可走的地方,黄线是最终规划出来的录像。
但在实际项目中,我们不可能用那么多个 div 元素来规划地图,性能是很差的。通常会在 canvas
里绘制地图。
下面是 canvas
版本的代码,
js
import React, { useState, useRef, useEffect } from 'react';
import PF from 'pathfinding';
// PathFindingApp 组件
const PathFindingApp = () => {
// 使用状态钩子管理网格、路径、起点和终点
const [grid, setGrid] = useState(new PF.Grid(10, 10)); // 创建一个 10x10 的网格
const [path, setPath] = useState([]); // 存储找到的路径
const [start, setStart] = useState([0, 0]); // 起点坐标
const [end, setEnd] = useState([9, 9]); // 终点坐标
const canvasRef = useRef(null); // 引用画布元素
// 切换网格单元的可行走状态
const toggleWalkable = (x, y) => {
const newGrid = grid.clone(); // 克隆当前网格
newGrid.setWalkableAt(x, y, !newGrid.isWalkableAt(x, y)); // 切换可行走状态
setGrid(newGrid); // 更新网格状态
};
// 寻找路径
const findPath = () => {
const finder = new PF.AStarFinder(); // 创建 A* 寻路算法实例
const newGrid = grid.clone(); // 克隆当前网格
const newPath = finder.findPath(start[0], start[1], end[0], end[1], newGrid); // 寻找路径
setPath(newPath); // 更新路径状态
};
// 绘制网格
const drawGrid = () => {
const canvas = canvasRef.current; // 获取画布引用
const context = canvas.getContext('2d'); // 获取绘图上下文
const cellSize = 30; // 单元格大小
// 清空画布
context.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格
for (let y = 0; y < 10; y++) {
for (let x = 0; x < 10; x++) {
const isWalkable = grid.isWalkableAt(x, y); // 判断单元格是否可行走
const isPath = path.some(([px, py]) => px === x && py === y); // 判断单元格是否在路径上
const isStart = x === start[0] && y === start[1]; // 判断单元格是否为起点
const isEnd = x === end[0] && y === end[1]; // 判断单元格是否为终点
// 设置单元格颜色
context.fillStyle = isStart ? 'green' : isEnd ? 'red' : isPath ? 'yellow' : isWalkable ? 'white' : 'black';
context.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); // 绘制单元格
context.strokeStyle = 'gray'; // 设置边框颜色
context.strokeRect(x * cellSize, y * cellSize, cellSize, cellSize); // 绘制单元格边框
}
}
};
// 使用 effect 钩子在网格或路径变化时重新绘制网格
useEffect(() => {
drawGrid();
}, [grid, path]);
// 处理画布点击事件
const handleCanvasClick = (e) => {
const rect = canvasRef.current.getBoundingClientRect(); // 获取画布位置
const x = Math.floor((e.clientX - rect.left) / 30); // 计算点击的网格 x 坐标
const y = Math.floor((e.clientY - rect.top) / 30); // 计算点击的网格 y 坐标
toggleWalkable(x, y); // 切换网格的可行走状态
};
// 组件返回的 JSX
return (
<div>
<canvas ref={canvasRef} width={300} height={300} onClick={handleCanvasClick} />
<button onClick={findPath}>Find Path</button> {/* 寻找路径按钮 */}
</div>
);
};
export default PathFindingApp; // 导出组件
以上就是本文的全部内容啦,如果本文对你有帮助的话,记得 点赞 + 关注 + 收藏 ~