纯前端网格路径规划:PathFinding.js的使用方法

点赞 + 关注 + 收藏 = 学会了

本文简介

在 Web 应用和游戏中,路径规划是一个核心功能,无论是在地图导航、策略游戏的单位移动,还是虚拟现实中的导航辅助,都离不开高效的路径查找算法。

在一个 Web 项目中,路径规划通常是放在后端做的。但其实前端也可以实现这个功能。在前端实现还可以降低服务器压力。

PathFinding.js 作为一个基于网格的纯前端路径规划工具,其轻量级、易于集成和强大的功能,是「纯前端」实现路径规划的一个不错选择。

本文将探讨 PathFinding.js 的基础使用方法。

在学习 PathFinding.js 之前,可以先体验一下它的能力。

安装

使用 npm 的方式将 PathFinding.js 安装到项目中

bash 复制代码
npm install pathfinding

基础用法

我用 React + PathFinding.js 举例说明。

使用 PathFinding.js 实现路径规划,需要做以下几步:

  1. 引入 PathFinding.js
  2. 创建网格地图(有地图才有路嘛)。
  3. 设置路障(哪里不可以走)。
  4. 设置起点和终点,调用路径规划算法,将可行路径规划出来。

一步步来,先看看这些功能要调哪些API。

1、引入 PathFinding.js

在使用 PathFinding.js 之前需要将其引入。

js 复制代码
import PF from 'pathfinding'

2、创建网格 (Grid)

PF.GridPathFinding.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):寻找从起点到终点的路径。
    • startXstartY:起点坐标。
    • endXendY:终点坐标。
    • grid:网格地图。
  • 返回值是路径点数组 [ [x1, y1], [x2, y2], ... ]

举个例子。

js 复制代码
const finder = new PF.AStarFinder();
const path = finder.findPath(0, 0, 9, 9, grid.clone());
console.log(path);

除了 AStarFinderPathFinding.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; // 导出组件

以上就是本文的全部内容啦,如果本文对你有帮助的话,记得 点赞 + 关注 + 收藏

相关推荐
大波V58 分钟前
vue3 使用docxtemplater 动态生成docx
前端·javascript·vue.js
1024小神9 分钟前
网页注入js代码实现获取请求的url和请求体内容,并获取响应体内容
前端·javascript
Fuzzyface10 分钟前
SPA是如何通过js不刷新页面但是更新浏览器的url的?
前端·javascript
simple丶10 分钟前
前端工程化:框架基础搭建
前端
用户25871419326324 分钟前
Vue3使用多线程处理文件分片任务
前端
不懂装懂的不懂26 分钟前
【vue3】中断请求、取消请求
前端·javascript·vue.js
鱼樱前端31 分钟前
React18+pnpm+Ts+React-Router v6从0-1搭建后台系统
前端·javascript·react.js
Epicurus32 分钟前
ES6箭头函数
前端
掘金0132 分钟前
手把手教你使用 FLV.js 在 Vue 项目中播放 FLV 视频
前端
前端没钱33 分钟前
vue3怎么和大模型交互?
前端