* 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 编写。
300 lines
6.7 KiB
Markdown
300 lines
6.7 KiB
Markdown
# Pathfinding System
|
||
|
||
`@esengine/pathfinding` provides a complete 2D pathfinding solution including A* algorithm, grid maps, navigation meshes, and path smoothing.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
npm install @esengine/pathfinding
|
||
```
|
||
|
||
## Quick Start
|
||
|
||
### Grid Map Pathfinding
|
||
|
||
```typescript
|
||
import { createGridMap, createAStarPathfinder } from '@esengine/pathfinding';
|
||
|
||
// Create 20x20 grid
|
||
const grid = createGridMap(20, 20);
|
||
|
||
// Set obstacles
|
||
grid.setWalkable(5, 5, false);
|
||
grid.setWalkable(5, 6, false);
|
||
|
||
// Create pathfinder
|
||
const pathfinder = createAStarPathfinder(grid);
|
||
|
||
// Find path
|
||
const result = pathfinder.findPath(0, 0, 15, 15);
|
||
|
||
if (result.found) {
|
||
console.log('Path found!');
|
||
console.log('Path:', result.path);
|
||
console.log('Cost:', result.cost);
|
||
}
|
||
```
|
||
|
||
### NavMesh Pathfinding
|
||
|
||
```typescript
|
||
import { createNavMesh } from '@esengine/pathfinding';
|
||
|
||
const navmesh = createNavMesh();
|
||
|
||
// Add polygon areas
|
||
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 }
|
||
]);
|
||
|
||
// Auto-build connections
|
||
navmesh.build();
|
||
|
||
// Find path
|
||
const result = navmesh.findPath(1, 1, 18, 8);
|
||
```
|
||
|
||
## Core Concepts
|
||
|
||
### IPathResult
|
||
|
||
```typescript
|
||
interface IPathResult {
|
||
readonly found: boolean; // Path found
|
||
readonly path: readonly IPoint[];// Path points
|
||
readonly cost: number; // Total cost
|
||
readonly nodesSearched: number; // Nodes searched
|
||
}
|
||
```
|
||
|
||
### IPathfindingOptions
|
||
|
||
```typescript
|
||
interface IPathfindingOptions {
|
||
maxNodes?: number; // Max search nodes (default 10000)
|
||
heuristicWeight?: number; // Heuristic weight (>1 faster but may be suboptimal)
|
||
allowDiagonal?: boolean; // Allow diagonal movement (default true)
|
||
avoidCorners?: boolean; // Avoid corner cutting (default true)
|
||
}
|
||
```
|
||
|
||
## Heuristic Functions
|
||
|
||
| Function | Use Case | Description |
|
||
|----------|----------|-------------|
|
||
| `manhattanDistance` | 4-directional | Manhattan distance |
|
||
| `euclideanDistance` | Any direction | Euclidean distance |
|
||
| `chebyshevDistance` | 8-directional | Diagonal cost = 1 |
|
||
| `octileDistance` | 8-directional | Diagonal cost = √2 (default) |
|
||
|
||
## Grid Map API
|
||
|
||
### createGridMap
|
||
|
||
```typescript
|
||
function createGridMap(
|
||
width: number,
|
||
height: number,
|
||
options?: IGridMapOptions
|
||
): GridMap
|
||
```
|
||
|
||
**Options:**
|
||
|
||
| Property | Type | Default | Description |
|
||
|----------|------|---------|-------------|
|
||
| `allowDiagonal` | `boolean` | `true` | Allow diagonal movement |
|
||
| `diagonalCost` | `number` | `√2` | Diagonal movement cost |
|
||
| `avoidCorners` | `boolean` | `true` | Avoid corner cutting |
|
||
| `heuristic` | `HeuristicFunction` | `octileDistance` | Heuristic function |
|
||
|
||
### Map Operations
|
||
|
||
```typescript
|
||
// Check/set walkability
|
||
grid.isWalkable(x, y);
|
||
grid.setWalkable(x, y, false);
|
||
|
||
// Set movement cost (e.g., swamp, sand)
|
||
grid.setCost(x, y, 2);
|
||
|
||
// Set rectangle region
|
||
grid.setRectWalkable(0, 0, 5, 5, false);
|
||
|
||
// Load from array (0=walkable, non-0=blocked)
|
||
grid.loadFromArray([
|
||
[0, 0, 0, 1, 0],
|
||
[0, 1, 0, 1, 0]
|
||
]);
|
||
|
||
// Load from string (.=walkable, #=blocked)
|
||
grid.loadFromString(`
|
||
.....
|
||
.#.#.
|
||
`);
|
||
|
||
// Export and reset
|
||
console.log(grid.toString());
|
||
grid.reset();
|
||
```
|
||
|
||
## A* Pathfinder API
|
||
|
||
```typescript
|
||
const pathfinder = createAStarPathfinder(grid);
|
||
|
||
const result = pathfinder.findPath(
|
||
startX, startY,
|
||
endX, endY,
|
||
{ maxNodes: 5000, heuristicWeight: 1.5 }
|
||
);
|
||
|
||
// Pathfinder is reusable
|
||
pathfinder.findPath(0, 0, 10, 10);
|
||
pathfinder.findPath(5, 5, 15, 15);
|
||
```
|
||
|
||
## NavMesh API
|
||
|
||
```typescript
|
||
const navmesh = createNavMesh();
|
||
|
||
// Add convex polygons
|
||
const id1 = navmesh.addPolygon(vertices1);
|
||
const id2 = navmesh.addPolygon(vertices2);
|
||
|
||
// Auto-detect shared edges
|
||
navmesh.build();
|
||
|
||
// Or manually set connections
|
||
navmesh.setConnection(id1, id2, {
|
||
left: { x: 10, y: 0 },
|
||
right: { x: 10, y: 10 }
|
||
});
|
||
|
||
// Query and pathfind
|
||
const polygon = navmesh.findPolygonAt(5, 5);
|
||
navmesh.isWalkable(5, 5);
|
||
const result = navmesh.findPath(1, 1, 18, 8);
|
||
```
|
||
|
||
## Path Smoothing
|
||
|
||
### Line of Sight Smoothing
|
||
|
||
Remove unnecessary waypoints:
|
||
|
||
```typescript
|
||
import { createLineOfSightSmoother } from '@esengine/pathfinding';
|
||
|
||
const smoother = createLineOfSightSmoother();
|
||
const smoothedPath = smoother.smooth(result.path, grid);
|
||
```
|
||
|
||
### Curve Smoothing
|
||
|
||
Catmull-Rom spline:
|
||
|
||
```typescript
|
||
import { createCatmullRomSmoother } from '@esengine/pathfinding';
|
||
|
||
const smoother = createCatmullRomSmoother(5, 0.5);
|
||
const curvedPath = smoother.smooth(result.path, grid);
|
||
```
|
||
|
||
### Combined Smoothing
|
||
|
||
```typescript
|
||
import { createCombinedSmoother } from '@esengine/pathfinding';
|
||
|
||
const smoother = createCombinedSmoother(5, 0.5);
|
||
const finalPath = smoother.smooth(result.path, grid);
|
||
```
|
||
|
||
### Line of Sight Functions
|
||
|
||
```typescript
|
||
import { bresenhamLineOfSight, raycastLineOfSight } from '@esengine/pathfinding';
|
||
|
||
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);
|
||
const hasLOS2 = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);
|
||
```
|
||
|
||
## Practical Examples
|
||
|
||
### Dynamic Obstacles
|
||
|
||
```typescript
|
||
class DynamicPathfinding {
|
||
private grid: GridMap;
|
||
private pathfinder: AStarPathfinder;
|
||
private dynamicObstacles: Set<string> = new Set();
|
||
|
||
addDynamicObstacle(x: number, y: number): void {
|
||
this.dynamicObstacles.add(`${x},${y}`);
|
||
this.grid.setWalkable(x, y, false);
|
||
}
|
||
|
||
removeDynamicObstacle(x: number, y: number): void {
|
||
this.dynamicObstacles.delete(`${x},${y}`);
|
||
this.grid.setWalkable(x, y, true);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Terrain Costs
|
||
|
||
```typescript
|
||
const grid = createGridMap(50, 50);
|
||
|
||
// Normal ground - cost 1 (default)
|
||
// Sand - cost 2
|
||
for (let y = 10; y < 20; y++) {
|
||
for (let x = 0; x < 50; x++) {
|
||
grid.setCost(x, y, 2);
|
||
}
|
||
}
|
||
|
||
// Swamp - cost 4
|
||
for (let y = 30; y < 35; y++) {
|
||
for (let x = 20; x < 30; x++) {
|
||
grid.setCost(x, y, 4);
|
||
}
|
||
}
|
||
```
|
||
|
||
## Blueprint Nodes
|
||
|
||
- `FindPath` - Find path
|
||
- `FindPathSmooth` - Find and smooth path
|
||
- `IsWalkable` - Check walkability
|
||
- `GetPathLength` - Get path point count
|
||
- `GetPathDistance` - Get total path distance
|
||
- `GetPathPoint` - Get specific path point
|
||
- `MoveAlongPath` - Move along path
|
||
- `HasLineOfSight` - Check line of sight
|
||
|
||
## Performance Tips
|
||
|
||
1. **Limit search range**: `{ maxNodes: 1000 }`
|
||
2. **Use heuristic weight**: `{ heuristicWeight: 1.5 }` (faster but may not be optimal)
|
||
3. **Reuse pathfinder instances**
|
||
4. **Use NavMesh for complex terrain**
|
||
5. **Choose appropriate heuristic for movement type**
|
||
|
||
## Grid vs NavMesh
|
||
|
||
| Feature | GridMap | NavMesh |
|
||
|---------|---------|---------|
|
||
| Use Case | Regular tile maps | Complex polygon terrain |
|
||
| Memory | Higher (width × height) | Lower (polygon count) |
|
||
| Precision | Grid-aligned | Continuous coordinates |
|
||
| Dynamic Updates | Easy | Requires rebuild |
|
||
| Setup Complexity | Simple | More complex |
|