Files
esengine/docs/modules/pathfinding/index.md
YHH 4a16e30794 docs(modules): 添加框架模块文档 (#350)
* docs(modules): 添加框架模块文档

添加以下模块的完整文档:
- FSM (状态机): 状态定义、转换条件、优先级、事件监听
- Timer (定时器): 定时器调度、冷却系统、服务令牌
- Spatial (空间索引): GridSpatialIndex、AOI 兴趣区域管理
- Pathfinding (寻路): A* 算法、网格地图、导航网格、路径平滑
- Procgen (程序化生成): 噪声函数、种子随机数、加权随机

所有文档均基于实际源码 API 编写,包含:
- 快速开始示例
- 完整 API 参考
- 实际使用案例
- 蓝图节点说明
- 最佳实践建议

* docs(modules): 添加 Blueprint 模块文档和所有模块英文版

新增中文文档:
- Blueprint (蓝图可视化脚本): VM、自定义节点、组合系统、触发器

新增英文文档 (docs/en/modules/):
- FSM: State machine API, transitions, ECS integration
- Timer: Timers, cooldowns, service tokens
- Spatial: Grid spatial index, AOI management
- Pathfinding: A*, grid map, NavMesh, path smoothing
- Procgen: Noise functions, seeded random, weighted random
- Blueprint: Visual scripting, custom nodes, composition

所有文档均基于实际源码 API 编写。
2025-12-26 20:02:21 +08:00

12 KiB
Raw Blame History

寻路系统 (Pathfinding)

@esengine/pathfinding 提供了完整的 2D 寻路解决方案,包括 A* 算法、网格地图、导航网格和路径平滑。

安装

npm install @esengine/pathfinding

快速开始

网格地图寻路

import { createGridMap, createAStarPathfinder } from '@esengine/pathfinding';

// 创建 20x20 的网格地图
const grid = createGridMap(20, 20);

// 设置障碍物
grid.setWalkable(5, 5, false);
grid.setWalkable(5, 6, false);
grid.setWalkable(5, 7, false);

// 创建寻路器
const pathfinder = createAStarPathfinder(grid);

// 查找路径
const result = pathfinder.findPath(0, 0, 15, 15);

if (result.found) {
    console.log('找到路径!');
    console.log('路径点:', result.path);
    console.log('总代价:', result.cost);
    console.log('搜索节点数:', result.nodesSearched);
}

导航网格寻路

import { createNavMesh } from '@esengine/pathfinding';

// 创建导航网格
const navmesh = createNavMesh();

// 添加多边形区域
navmesh.addPolygon([
    { x: 0, y: 0 }, { x: 10, y: 0 },
    { x: 10, y: 10 }, { x: 0, y: 10 }
]);

navmesh.addPolygon([
    { x: 10, y: 0 }, { x: 20, y: 0 },
    { x: 20, y: 10 }, { x: 10, y: 10 }
]);

// 自动建立连接
navmesh.build();

// 寻路
const result = navmesh.findPath(1, 1, 18, 8);

核心概念

IPoint - 坐标点

interface IPoint {
    readonly x: number;
    readonly y: number;
}

IPathResult - 寻路结果

interface IPathResult {
    readonly found: boolean;       // 是否找到路径
    readonly path: readonly IPoint[]; // 路径点列表
    readonly cost: number;         // 路径总代价
    readonly nodesSearched: number; // 搜索的节点数
}

IPathfindingOptions - 寻路配置

interface IPathfindingOptions {
    maxNodes?: number;        // 最大搜索节点数(默认 10000
    heuristicWeight?: number; // 启发式权重(>1 更快但可能非最优)
    allowDiagonal?: boolean;  // 是否允许对角移动(默认 true
    avoidCorners?: boolean;   // 是否避免穿角(默认 true
}

启发式函数

模块提供了四种启发式函数:

函数 适用场景 说明
manhattanDistance 4方向移动 曼哈顿距离,只考虑水平/垂直
euclideanDistance 任意方向 欧几里得距离,直线距离
chebyshevDistance 8方向移动 切比雪夫距离,对角线代价为 1
octileDistance 8方向移动 八角距离,对角线代价为 √2默认
import { manhattanDistance, octileDistance } from '@esengine/pathfinding';

// 自定义启发式
const grid = createGridMap(20, 20, {
    heuristic: manhattanDistance // 使用曼哈顿距离
});

网格地图 API

createGridMap

function createGridMap(
    width: number,
    height: number,
    options?: IGridMapOptions
): GridMap

配置选项:

属性 类型 默认值 描述
allowDiagonal boolean true 允许对角移动
diagonalCost number √2 对角移动代价
avoidCorners boolean true 避免穿角
heuristic HeuristicFunction octileDistance 启发式函数

地图操作

// 检查/设置可通行性
grid.isWalkable(x, y);
grid.setWalkable(x, y, false);

// 设置移动代价(如沼泽、沙地)
grid.setCost(x, y, 2); // 代价为 2默认 1

// 设置矩形区域
grid.setRectWalkable(0, 0, 5, 5, false);

// 从数组加载0=可通行非0=障碍)
grid.loadFromArray([
    [0, 0, 0, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 1, 0, 0, 0]
]);

// 从字符串加载(.=可通行,#=障碍)
grid.loadFromString(`
.....
.#.#.
.#...
`);

// 导出为字符串
console.log(grid.toString());

// 重置所有节点为可通行
grid.reset();

方向常量

import { DIRECTIONS_4, DIRECTIONS_8 } from '@esengine/pathfinding';

// 4方向上下左右
DIRECTIONS_4 // [{ dx: 0, dy: -1 }, { dx: 1, dy: 0 }, ...]

// 8方向含对角线
DIRECTIONS_8 // [{ dx: 0, dy: -1 }, { dx: 1, dy: -1 }, ...]

A* 寻路器 API

createAStarPathfinder

function createAStarPathfinder(map: IPathfindingMap): AStarPathfinder

findPath

const result = pathfinder.findPath(
    startX, startY,
    endX, endY,
    {
        maxNodes: 5000,       // 限制搜索节点数
        heuristicWeight: 1.5  // 加速但可能非最优
    }
);

重用寻路器

// 寻路器可重用,内部会自动清理状态
pathfinder.findPath(0, 0, 10, 10);
pathfinder.findPath(5, 5, 15, 15);

// 手动清理(可选)
pathfinder.clear();

导航网格 API

createNavMesh

function createNavMesh(): NavMesh

构建导航网格

const navmesh = createNavMesh();

// 添加凸多边形
const id1 = navmesh.addPolygon([
    { x: 0, y: 0 }, { x: 10, y: 0 },
    { x: 10, y: 10 }, { x: 0, y: 10 }
]);

const id2 = navmesh.addPolygon([
    { x: 10, y: 0 }, { x: 20, y: 0 },
    { x: 20, y: 10 }, { x: 10, y: 10 }
]);

// 方式1自动检测共享边并建立连接
navmesh.build();

// 方式2手动设置连接
navmesh.setConnection(id1, id2, {
    left: { x: 10, y: 0 },
    right: { x: 10, y: 10 }
});

查询和寻路

// 查找包含点的多边形
const polygon = navmesh.findPolygonAt(5, 5);

// 检查位置是否可通行
navmesh.isWalkable(5, 5);

// 寻路(内部使用漏斗算法优化路径)
const result = navmesh.findPath(1, 1, 18, 8);

路径平滑 API

视线简化

移除不必要的中间点:

import { createLineOfSightSmoother } from '@esengine/pathfinding';

const smoother = createLineOfSightSmoother();
const smoothedPath = smoother.smooth(result.path, grid);

// 原路径: [(0,0), (1,1), (2,2), (3,3), (4,4)]
// 简化后: [(0,0), (4,4)]

曲线平滑

使用 Catmull-Rom 样条曲线:

import { createCatmullRomSmoother } from '@esengine/pathfinding';

const smoother = createCatmullRomSmoother(
    5,   // segments - 每段插值点数
    0.5  // tension - 张力 (0-1)
);

const curvedPath = smoother.smooth(result.path, grid);

组合平滑

先简化再曲线平滑:

import { createCombinedSmoother } from '@esengine/pathfinding';

const smoother = createCombinedSmoother(5, 0.5);
const finalPath = smoother.smooth(result.path, grid);

视线检测函数

import { bresenhamLineOfSight, raycastLineOfSight } from '@esengine/pathfinding';

// Bresenham 算法(快速,网格对齐)
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);

// 射线投射(精确,支持浮点坐标)
const hasLOS = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);

实际示例

游戏角色移动

class MovementSystem {
    private grid: GridMap;
    private pathfinder: AStarPathfinder;
    private smoother: CombinedSmoother;

    constructor(width: number, height: number) {
        this.grid = createGridMap(width, height);
        this.pathfinder = createAStarPathfinder(this.grid);
        this.smoother = createCombinedSmoother();
    }

    findPath(from: IPoint, to: IPoint): IPoint[] | null {
        const result = this.pathfinder.findPath(
            from.x, from.y,
            to.x, to.y
        );

        if (!result.found) {
            return null;
        }

        // 平滑路径
        return this.smoother.smooth(result.path, this.grid);
    }

    setObstacle(x: number, y: number): void {
        this.grid.setWalkable(x, y, false);
    }

    setTerrain(x: number, y: number, cost: number): void {
        this.grid.setCost(x, y, cost);
    }
}

动态障碍物

class DynamicPathfinding {
    private grid: GridMap;
    private pathfinder: AStarPathfinder;
    private dynamicObstacles: Set<string> = new Set();

    addDynamicObstacle(x: number, y: number): void {
        const key = `${x},${y}`;
        if (!this.dynamicObstacles.has(key)) {
            this.dynamicObstacles.add(key);
            this.grid.setWalkable(x, y, false);
        }
    }

    removeDynamicObstacle(x: number, y: number): void {
        const key = `${x},${y}`;
        if (this.dynamicObstacles.has(key)) {
            this.dynamicObstacles.delete(key);
            this.grid.setWalkable(x, y, true);
        }
    }

    findPath(from: IPoint, to: IPoint): IPathResult {
        return this.pathfinder.findPath(from.x, from.y, to.x, to.y);
    }
}

不同地形代价

// 设置不同地形的移动代价
const grid = createGridMap(50, 50);

// 普通地面 - 代价 1默认
// 沙地 - 代价 2
for (let y = 10; y < 20; y++) {
    for (let x = 0; x < 50; x++) {
        grid.setCost(x, y, 2);
    }
}

// 沼泽 - 代价 4
for (let y = 30; y < 35; y++) {
    for (let x = 20; x < 30; x++) {
        grid.setCost(x, y, 4);
    }
}

// 寻路时会自动考虑地形代价
const result = pathfinder.findPath(0, 0, 49, 49);

分层寻路

对于大型地图,使用层级化寻路:

class HierarchicalPathfinding {
    private coarseGrid: GridMap;  // 粗粒度网格
    private fineGrid: GridMap;    // 细粒度网格
    private coarsePathfinder: AStarPathfinder;
    private finePathfinder: AStarPathfinder;
    private cellSize = 10;

    findPath(from: IPoint, to: IPoint): IPoint[] {
        // 1. 在粗粒度网格上寻路
        const coarseFrom = this.toCoarse(from);
        const coarseTo = this.toCoarse(to);
        const coarseResult = this.coarsePathfinder.findPath(
            coarseFrom.x, coarseFrom.y,
            coarseTo.x, coarseTo.y
        );

        if (!coarseResult.found) {
            return [];
        }

        // 2. 在每个粗粒度单元内进行细粒度寻路
        const finePath: IPoint[] = [];
        // ... 详细实现略
        return finePath;
    }

    private toCoarse(p: IPoint): IPoint {
        return {
            x: Math.floor(p.x / this.cellSize),
            y: Math.floor(p.y / this.cellSize)
        };
    }
}

蓝图节点

Pathfinding 模块提供了可视化脚本支持的蓝图节点:

  • FindPath - 查找路径
  • FindPathSmooth - 查找并平滑路径
  • IsWalkable - 检查位置是否可通行
  • GetPathLength - 获取路径点数
  • GetPathDistance - 获取路径总距离
  • GetPathPoint - 获取路径上的指定点
  • MoveAlongPath - 沿路径移动
  • HasLineOfSight - 检查视线

性能优化

  1. 限制搜索范围

    pathfinder.findPath(x1, y1, x2, y2, { maxNodes: 1000 });
    
  2. 使用启发式权重

    // 权重 > 1 会更快但可能不是最优路径
    pathfinder.findPath(x1, y1, x2, y2, { heuristicWeight: 1.5 });
    
  3. 复用寻路器实例

    // 创建一次,多次使用
    const pathfinder = createAStarPathfinder(grid);
    
  4. 使用导航网格

    • 对于复杂地形NavMesh 比网格寻路更高效
    • 多边形数量远少于网格单元格数量
  5. 选择合适的启发式

    • 4方向移动用 manhattanDistance
    • 8方向移动用 octileDistance(默认)

网格 vs 导航网格

特性 GridMap NavMesh
适用场景 规则瓦片地图 复杂多边形地形
内存占用 较高 (width × height) 较低 (多边形数)
精度 网格对齐 连续坐标
动态修改 容易 需要重建
设置复杂度 简单 较复杂