pathfinding

module
v0.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MIT

README

Pathfinding

一个全面的 Go 寻路库,支持多种网格类型和搜索算法。

安装

go get github.com/actfuns/pathfinding

快速开始

package main

import (
    "fmt"
    "github.com/actfuns/pathfinding/grid"
)

func main() {
    // 定义地形:0 = 可通行,非零 = 障碍物
    terrain := [][]int{
        {0, 0, 0, 0, 0},
        {0, 1, 1, 1, 0},
        {0, 0, 0, 0, 0},
        {0, 0, 0, 1, 0},
        {0, 0, 0, 0, 0},
    }

    // 创建正交网格(默认瓦片大小 1×1 世界单位)
    g := grid.NewOrthogonalGrid(terrain)

    // 在世界坐标系中寻路
    path := g.FindPathWorld(0, 0, 4, 4)
    fmt.Println("路径:", path)

    // 在运行时修改地形
    g.SetWalkableAt(2, 2, false)
}

也可创建全空网格:

m := grid.NewMatrix(100, 100)    // 100×100 全空
g := grid.NewOrthogonalGrid(m)
瓦片权重(Tile Weight Cost)

v0.5.0+ 支持每个瓦片的移动成本权重。A*、Dijkstra、IDA*、JPS、JPS+ 在计算步进成本时自动乘以目标瓦片的 Weight 值。

g := grid.NewOrthogonalGrid(matrix)

// 设置地形权重:道路(0.3),草地(1.0),沼泽(5.0),岩浆(10.0)
g.SetWeightAt(2, 3, 5.0)
g.SetWeightAt(3, 3, 5.0)
g.SetWeightAt(5, 0, 0.3)  // 低成本道路

f := finder.NewAStarFinder()
path := f.FindPath(0, 0, 7, 7, g)
// A* 会自动选择总成本最低的路径,而非步骤最少的路径

默认权重为 1.0,设为 0 表示不可通行。所有成本感知算法(A*、Dijkstra、IDA*、JPS、JPS+)都支持。BFS 和 Best-First Search 不使用成本计算,因此不受权重影响。

SVG 可视化

RenderSVG 根据瓦片权重自动着色,并显示图例:

// 默认着色(nil = 使用 DefaultSVGOpts)
svg := g.RenderSVG(nil, path)

// 自定义颜色映射
svg := g.RenderSVG(&grid.SVGOpts{
    WeightColors: [][2]string{
        {"0.3", "#bbdefb"}, // ≤0.3 蓝色(道路)
        {"1.0", "#c8e6c9"}, // ≤1.0 绿色(草地)
        {"5.0", "#ffcc80"}, // ≤5.0 橙色(沼泽)
        {"+Inf", "#ef5350"},// >5.0 红色(极端)
    },
    LegendEntries: [][2]string{
        {"#bbdefb", "Road"},
        {"#c8e6c9", "Grass"},
        {"#ffcc80", "Swamp"},
        {"#555555", "Wall"},
    },
}, path)

支持多路径叠加(第一条蓝色实线,第二条红色虚线):

svg := g.RenderSVG(nil, pathWithWeights, pathWithoutWeights)

环境变量 NAVPATH_DUMP_SVG=1 可在测试时自动输出 SVG 到 grid/testdata/svg/

使用其他寻路算法
import "github.com/actfuns/pathfinding/finder"

// 替换默认的 A* 为 JPS
g := grid.NewOrthogonalGrid(terrain,
    grid.WithOrthogonalFinder(finder.NewJumpPointFinder()),
)

网格类型

类型 说明
正交网格 (Orthogonal) 标准方格网格,支持自定义 TileSize 转换世界坐标
六边形网格 (Hexagonal) 六边形瓦片网格(轴向坐标),支持 flat-top / pointy-top
交错网格 (Staggered) 45 度等距/交错瓦片网格(钻石形瓦片)
Grid 接口

所有网格类型实现统一的 grid.Grid 接口:

  • 瓦片坐标查询 — Width, Height, IsInside, IsWalkableAt, SetWalkableAt, GetNodeAt, ObstacleCount
  • 瓦片权重 — SetWeightAt, GetWeightAt(v0.5.0+)
  • 流场群体寻路 — flowfield.New / GetDirection(O(1) 多人查表,v0.5.0+)
  • 世界坐标转换 — WorldToTile, TileToWorld, IsWalkableAtWorld, SetWalkableAtWorld
  • 最近可通行点 — FindNearestWalkable, FindNearestWalkableTile
  • 寻路 — FindPath, FindSmoothPath, SmoothenTilePath
  • 渲染 — RenderSVG(cfg, paths...)(权重着色 + 多路径 + 图例,v0.5.0+)

寻路算法

算法 双向 说明
A* 经典启发式最短路径
Dijkstra 加权最短路径(无启发式)
Best-First Search 贪心最佳优先搜索
Breadth-First Search 广度优先遍历
IDA* 迭代加深 A*,内存占用极低
JPS (Jump Point Search) 跳点搜索,利用网格对称性加速
JPS+ (Jump Point Search Plus) JPS 优化变体,Precompute 跳点表,O(1) 查表替换递归扫描
HPA* 层级寻路,分块抽象加速大地图
Flow Field 流场群体寻路,一次 Dijkstra 所有人 O(1) 查方向
JPS 变体

根据对角线移动规则提供四个优化变体:

变体 对角线规则 适用场景
Never 禁止对角线 四方向移动
Always 始终允许对角线 开阔地形
NoObstacles 无障碍时允许对角线 半开阔地形
AtMostOne 最多一个障碍时允许对角线 复杂障碍布局
JPS+ (Jump Point Search Plus)

JPS+ 是 JPS 的预计算优化变体,用 O(1) 跳点距离表替换递归 jump() 扫描:

特性 说明
Precompute 两阶段预计算:Phase 1 检测 Primary Jump Points,Phase 2 计算 8 方向距离表
export/import PrecomputedData() / LoadPrecomputed() 支持烘焙数据导出为文件,服务端启动直接加载
searchSeq 全局唯一 所有 finder 的 searchSeq 通过全局原子计数器初始化,不同 finder 可安全共享同一 grid

适用场景:障碍物密集的静态小地图(如游戏房间内布局固定的障碍地图)。开阔地形下利用预计算距离表可直接跳跃到目标,反而是 JPS+ 最快的场景(100×100 仅 0.6µs)。

import (
    "github.com/actfuns/pathfinding/finder"
    "github.com/actfuns/pathfinding/grid"
)

matrix := [][]int{{0, 0, 0}, {0, 1, 0}, {0, 0, 0}}
g := grid.NewOrthogonalGrid(matrix)

f := finder.NewJPSPlusFinder(finder.WithDiagonal(finder.DiagonalNever))
f.Precompute(g)

path := f.FindPath(0, 0, 2, 2, g)

// 导出烘焙数据供后续启动直接加载
data, _ := f.PrecomputedData()
// os.WriteFile("map_data.bin", data, 0644)

高级系统

HPA* (Hierarchical Pathfinding A*)

层级寻路,通过抽象分层加速大地图寻路。核心优化:

优化 说明
Portal Compression (P0) 将相邻可通行边缘合并为连续 Portal,减少抽象图节点数 4~8×
String Pulling (P1) 对最终路径进行 LOS 平滑,消除层级抽象产生的冗余拐点
Allocation Optimization (P2) 复用内部缓冲区(portalPathBuf、waypointsBuf、waypointsResult),预热后零分配
Portal Path Cache (P3) 缓存 (startChunk, endChunk) → portal 路径,重复查询直接复用,避免抽象层 A*
import (
    "github.com/actfuns/pathfinding/grid"
    "github.com/actfuns/pathfinding/hpa"
)

matrix := generateGrid(500, 500, 0.1)
g := grid.NewOrthogonalGrid(matrix)

f := hpa.NewHPAFinder(
    hpa.WithChunkSize(32),
    hpa.WithStringPulling(true),
)
f.Build(g)

path := f.FindPath(0, 0, 499, 499, g)
Waypoint Graph

航点图寻路,基于预计算节点连通性。适用于静态路网场景。

优化 说明
空间哈希索引 cellSize=64 网格分区,FindClosest/FindClosestWithLOS 降至 O(1)~O(k)
ConnectNearby 优化 从 O(n²) 降至 O(n×k),仅检查邻近 cell 内的节点对
LOS 边缘过滤 节点连接自动检测障碍物遮挡,仅保留可视连接
EdgeState 系统 支持 Static / Dynamic / Null 三种边状态,可动态阻塞
import "github.com/actfuns/pathfinding/waypoint"

g := waypoint.NewWaypointGraph()
a := g.AddNode(0, 0)
b := g.AddNode(10, 0)
c := g.AddNode(10, 10)

a.Connect(b, waypoint.EdgeStateStatic)
b.Connect(c)

f := waypoint.NewWaypointFinder(g)
path := f.FindPath(0, 0, 10, 10, grid)
OrthogonalGrid 分配与路径平滑

OrthogonalGrid 对 A* 寻路做了极致的内存优化:

优化 说明
TileSize 转换 世界坐标 ↔ 瓦片坐标自动换算,支持非 1:1 比例
零分配 A* AStar Finder 内部复用 Node 和 OpenSet 缓冲区,benchmark 显示 0 B/op
LOS Smoothing FindSmoothPath 基于 Bresenham 直线可视性消除冗余路径点,开销仅 ~25%
可插拔 Finder 通过 WithOrthogonalFinder() 可替换底层寻路算法
障碍物计数 obstacleCount 跟踪不可通行瓦片数,全空时 FindPath 直接返回线段,不走算法

性能基准

以下基准测试在 Intel i5-13400 上运行。测试三种障碍密度(固定种子,可复现):

  • Open (0%) — 全空网格
  • Sparse (10%) — 稀疏障碍
  • Dense (20%) — 密集障碍

运行命令:go test -bench=BenchmarkCompare -benchmem ./finder/

规模 密度 算法 时间/op 内存/op 分配/op
100×100 Open A* 150 µs 0 B 0
JPS 122 µs 88 B 3
JPS+ 0.6 µs 24 B 1
Sparse A* 294 µs 0 B 0
JPS 29 µs 12.5 KB 392
JPS+ 19 µs 24 B 1
Dense A* 135 µs 0 B 0
JPS 24 µs 12.9 KB 375
JPS+ 20 µs 24 B 1
200×200 Open A* 1.10 ms 0 B 0
JPS 605 µs 88 B 3
JPS+ 19 µs 24 B 1
Sparse A* 667 µs 0 B 0
JPS 62 µs 25.2 KB 795
JPS+ 45 µs 24 B 1
Dense A* 662 µs 0 B 0
JPS 54 µs 27.7 KB 815
JPS+ 53 µs 24 B 1

关键发现:

  • 开阔地形 (0%) — JPS+ 利用预计算跳点表直接跳到目标,100×100 仅 0.6µs,比 A* 快 250×
  • 有障碍时 — JPS 和 JPS+ 表现接近,比 A* 快 3~12×
  • A* 零分配 — 内部复用 Node/OpenSet 缓冲区,全程 0 B/op
  • JPS+ 近乎零分配 — 内联 Bresenham 消除 ExpandPath 分配,仅返回路径的 1 次分配
Waypoint 寻路
基准 时间/op 内存/op 分配/op
FindPath(预热后) 139,615 ns 1 B 0
ConnectNearby(676 节点) 1,508,913 ns 244,768 B 6,901
各规模对比
规模 A* JPS JPS+ HPA*
100×100 (10% obs) 294 µs 29 µs 19 µs 2,911 ns
200×200 (10% obs) 667 µs 62 µs 45 µs 3,782 ns
500×500 (10% obs) 25 ms 46 ms 35 µs

HPA* 在 500×500 上相比 A* 加速约 730×

命令

# 运行全部测试(压力测试需 -tags stress)
go test ./... -count=1 -vet=all

# 压力测试(构建标签保护,默认不运行)
go test -tags stress -v -run Stress ./grid/

# 基准测试(A* / JPS / JPS+ 对比)
go test -bench=BenchmarkCompare -benchmem ./finder/
# 基准测试(Orthogonal / Hex / Staggered 网格)
go test -bench=BenchmarkOrthogonalAlloc -benchmem ./grid/
go test -bench=BenchmarkOrthogonal100 -benchmem ./grid/
# 基准测试(HPA* / Waypoint)
go test -bench=. -benchmem ./hpa/
go test -bench=. -benchmem ./waypoint/

# 生成 SVG 可视化(有权重场景自动着色 + 图例)
NAVPATH_DUMP_SVG=1 go test -run "TestOrthogonalSVG|TestHexSVG|TestStaggeredSVG" ./grid/
ls grid/testdata/svg/*weighted*.svg

示例

examples/ 目录包含可直接运行的示例:

# 基本 A* 寻路
go run examples/basic/

# 权重地形对比(显示权重如何影响路径选择)
go run examples/weighted/

# SVG 可视化输出
go run examples/svg/ | head -5

# 流场群体寻路
go run examples/flowfield/

版本历史

v0.6.0 — Flow Field 流场群体寻路
  • flowfield/ 包:New/NewMulti 单目标/多目标建场
  • Reset/ResetMulti 切换目标,复用内存
  • GetDirection O(1) 方向查询(0.5ns)
  • GetCost/FindPath 调试支持
  • WithDiagonal 对角线支持
  • 瓦片权重自动纳入
  • examples/flowfield/ 多单位平滑移动示例
v0.5.x — Tile Weight Cost 权重系统 + SVG 着色渲染
  • finder.Node.Weight 字段,A*/BiA*/IDA*/JPS/JPS+ 步进成本 × tile.Weight
  • grid.Grid.SetWeightAt / GetWeightAt,三层网格实现
  • RenderSVG(cfg, paths...) 权重着色 + 多路径 + 右侧图例
  • SVGOpts 自定义颜色映射和图例标签
  • DefaultSVGOpts 全局默认配置
  • examples/ 目录(basic / weighted / svg)
  • godoc 注释补全
v0.4.0
  • JPS+/HPA* 性能优化、Bresenham 内联、全局原子计数器

设计原则

  • 零分配热路径 — A* 和 HPA* 在预热后达到 0 B/op,减少 GC 压力
  • 接口统一 — 所有算法实现 finder.Finder 接口,可互换
  • 可插拔OrthogonalGrid.WithOrthogonalFinder() 允许运行时替换寻路算法
  • Option 模式 — 所有配置项通过函数选项安全设置
  • 接口分离finder.Grid 只暴露寻路所需方法,grid.Grid 提供完整的消费者接口

文档

  • Finder API — 所有寻路算法的通用接口
  • Grid — 寻路算法所需的网格接口
  • Grid 实现 — 正交、六边形、交错三种网格类型
  • HPA* — 层级寻路(Portal Compression + String Pulling + Path Cache)
  • Waypoint — 航点图寻路(空间索引 + LOS 过滤)
  • Examples — 可直接运行的示例(basic/weighted/svg/flowfield)

许可

MIT License — 详见 LICENSE 文件。

Directories

Path Synopsis
examples
basic command
Example: Basic A* pathfinding on a grid with obstacles.
Example: Basic A* pathfinding on a grid with obstacles.
flowfield command
svg command
Example: Generate SVG visualization of a weighted grid with path overlay.
Example: Generate SVG visualization of a weighted grid with path overlay.
weighted command
Example: Weighted terrain pathfinding.
Example: Weighted terrain pathfinding.
Package flowfield implements Flow Field pathfinding for efficient group movement.
Package flowfield implements Flow Field pathfinding for efficient group movement.
Package gridutil provides utility functions for grid-based pathfinding.
Package gridutil provides utility functions for grid-based pathfinding.
Package waypoint provides graph-based pathfinding on arbitrary waypoint graphs.
Package waypoint provides graph-based pathfinding on arbitrary waypoint graphs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL