* 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 编写。
503 lines
12 KiB
Markdown
503 lines
12 KiB
Markdown
# 寻路系统 (Pathfinding)
|
||
|
||
`@esengine/pathfinding` 提供了完整的 2D 寻路解决方案,包括 A* 算法、网格地图、导航网格和路径平滑。
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
npm install @esengine/pathfinding
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 网格地图寻路
|
||
|
||
```typescript
|
||
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);
|
||
}
|
||
```
|
||
|
||
### 导航网格寻路
|
||
|
||
```typescript
|
||
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 - 坐标点
|
||
|
||
```typescript
|
||
interface IPoint {
|
||
readonly x: number;
|
||
readonly y: number;
|
||
}
|
||
```
|
||
|
||
### IPathResult - 寻路结果
|
||
|
||
```typescript
|
||
interface IPathResult {
|
||
readonly found: boolean; // 是否找到路径
|
||
readonly path: readonly IPoint[]; // 路径点列表
|
||
readonly cost: number; // 路径总代价
|
||
readonly nodesSearched: number; // 搜索的节点数
|
||
}
|
||
```
|
||
|
||
### IPathfindingOptions - 寻路配置
|
||
|
||
```typescript
|
||
interface IPathfindingOptions {
|
||
maxNodes?: number; // 最大搜索节点数(默认 10000)
|
||
heuristicWeight?: number; // 启发式权重(>1 更快但可能非最优)
|
||
allowDiagonal?: boolean; // 是否允许对角移动(默认 true)
|
||
avoidCorners?: boolean; // 是否避免穿角(默认 true)
|
||
}
|
||
```
|
||
|
||
## 启发式函数
|
||
|
||
模块提供了四种启发式函数:
|
||
|
||
| 函数 | 适用场景 | 说明 |
|
||
|------|----------|------|
|
||
| `manhattanDistance` | 4方向移动 | 曼哈顿距离,只考虑水平/垂直 |
|
||
| `euclideanDistance` | 任意方向 | 欧几里得距离,直线距离 |
|
||
| `chebyshevDistance` | 8方向移动 | 切比雪夫距离,对角线代价为 1 |
|
||
| `octileDistance` | 8方向移动 | 八角距离,对角线代价为 √2(默认) |
|
||
|
||
```typescript
|
||
import { manhattanDistance, octileDistance } from '@esengine/pathfinding';
|
||
|
||
// 自定义启发式
|
||
const grid = createGridMap(20, 20, {
|
||
heuristic: manhattanDistance // 使用曼哈顿距离
|
||
});
|
||
```
|
||
|
||
## 网格地图 API
|
||
|
||
### createGridMap
|
||
|
||
```typescript
|
||
function createGridMap(
|
||
width: number,
|
||
height: number,
|
||
options?: IGridMapOptions
|
||
): GridMap
|
||
```
|
||
|
||
**配置选项:**
|
||
|
||
| 属性 | 类型 | 默认值 | 描述 |
|
||
|------|------|--------|------|
|
||
| `allowDiagonal` | `boolean` | `true` | 允许对角移动 |
|
||
| `diagonalCost` | `number` | `√2` | 对角移动代价 |
|
||
| `avoidCorners` | `boolean` | `true` | 避免穿角 |
|
||
| `heuristic` | `HeuristicFunction` | `octileDistance` | 启发式函数 |
|
||
|
||
### 地图操作
|
||
|
||
```typescript
|
||
// 检查/设置可通行性
|
||
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();
|
||
```
|
||
|
||
### 方向常量
|
||
|
||
```typescript
|
||
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
|
||
|
||
```typescript
|
||
function createAStarPathfinder(map: IPathfindingMap): AStarPathfinder
|
||
```
|
||
|
||
### findPath
|
||
|
||
```typescript
|
||
const result = pathfinder.findPath(
|
||
startX, startY,
|
||
endX, endY,
|
||
{
|
||
maxNodes: 5000, // 限制搜索节点数
|
||
heuristicWeight: 1.5 // 加速但可能非最优
|
||
}
|
||
);
|
||
```
|
||
|
||
### 重用寻路器
|
||
|
||
```typescript
|
||
// 寻路器可重用,内部会自动清理状态
|
||
pathfinder.findPath(0, 0, 10, 10);
|
||
pathfinder.findPath(5, 5, 15, 15);
|
||
|
||
// 手动清理(可选)
|
||
pathfinder.clear();
|
||
```
|
||
|
||
## 导航网格 API
|
||
|
||
### createNavMesh
|
||
|
||
```typescript
|
||
function createNavMesh(): NavMesh
|
||
```
|
||
|
||
### 构建导航网格
|
||
|
||
```typescript
|
||
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 }
|
||
});
|
||
```
|
||
|
||
### 查询和寻路
|
||
|
||
```typescript
|
||
// 查找包含点的多边形
|
||
const polygon = navmesh.findPolygonAt(5, 5);
|
||
|
||
// 检查位置是否可通行
|
||
navmesh.isWalkable(5, 5);
|
||
|
||
// 寻路(内部使用漏斗算法优化路径)
|
||
const result = navmesh.findPath(1, 1, 18, 8);
|
||
```
|
||
|
||
## 路径平滑 API
|
||
|
||
### 视线简化
|
||
|
||
移除不必要的中间点:
|
||
|
||
```typescript
|
||
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 样条曲线:
|
||
|
||
```typescript
|
||
import { createCatmullRomSmoother } from '@esengine/pathfinding';
|
||
|
||
const smoother = createCatmullRomSmoother(
|
||
5, // segments - 每段插值点数
|
||
0.5 // tension - 张力 (0-1)
|
||
);
|
||
|
||
const curvedPath = smoother.smooth(result.path, grid);
|
||
```
|
||
|
||
### 组合平滑
|
||
|
||
先简化再曲线平滑:
|
||
|
||
```typescript
|
||
import { createCombinedSmoother } from '@esengine/pathfinding';
|
||
|
||
const smoother = createCombinedSmoother(5, 0.5);
|
||
const finalPath = smoother.smooth(result.path, grid);
|
||
```
|
||
|
||
### 视线检测函数
|
||
|
||
```typescript
|
||
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);
|
||
```
|
||
|
||
## 实际示例
|
||
|
||
### 游戏角色移动
|
||
|
||
```typescript
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 动态障碍物
|
||
|
||
```typescript
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 不同地形代价
|
||
|
||
```typescript
|
||
// 设置不同地形的移动代价
|
||
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);
|
||
```
|
||
|
||
### 分层寻路
|
||
|
||
对于大型地图,使用层级化寻路:
|
||
|
||
```typescript
|
||
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. **限制搜索范围**
|
||
```typescript
|
||
pathfinder.findPath(x1, y1, x2, y2, { maxNodes: 1000 });
|
||
```
|
||
|
||
2. **使用启发式权重**
|
||
```typescript
|
||
// 权重 > 1 会更快但可能不是最优路径
|
||
pathfinder.findPath(x1, y1, x2, y2, { heuristicWeight: 1.5 });
|
||
```
|
||
|
||
3. **复用寻路器实例**
|
||
```typescript
|
||
// 创建一次,多次使用
|
||
const pathfinder = createAStarPathfinder(grid);
|
||
```
|
||
|
||
4. **使用导航网格**
|
||
- 对于复杂地形,NavMesh 比网格寻路更高效
|
||
- 多边形数量远少于网格单元格数量
|
||
|
||
5. **选择合适的启发式**
|
||
- 4方向移动用 `manhattanDistance`
|
||
- 8方向移动用 `octileDistance`(默认)
|
||
|
||
## 网格 vs 导航网格
|
||
|
||
| 特性 | GridMap | NavMesh |
|
||
|------|---------|---------|
|
||
| 适用场景 | 规则瓦片地图 | 复杂多边形地形 |
|
||
| 内存占用 | 较高 (width × height) | 较低 (多边形数) |
|
||
| 精度 | 网格对齐 | 连续坐标 |
|
||
| 动态修改 | 容易 | 需要重建 |
|
||
| 设置复杂度 | 简单 | 较复杂 |
|