Skip to content

Pathfinding System

@esengine/pathfinding provides a complete 2D pathfinding solution with multiple algorithms:

  • A* Algorithm - General-purpose pathfinding
  • GridPathfinder - High-performance grid pathfinder with multiple modes
  • JPS (Jump Point Search) - Acceleration for open maps
  • HPA* (Hierarchical Pathfinding) - Optimization for large maps
  • Incremental Pathfinding - Time-sliced execution, non-blocking
  • Path Caching - Speed up repeated queries
Terminal window
npm install @esengine/pathfinding

The package provides three separate entry points for better tree-shaking:

// Core pathfinding (no external dependencies except math)
import { AStarPathfinder, GridPathfinder, JPSPathfinder } from '@esengine/pathfinding';
// ECS components and systems (requires @esengine/ecs-framework)
import { PathfindingSystem, PathfindingAgentComponent } from '@esengine/pathfinding/ecs';
// Blueprint nodes (requires @esengine/blueprint)
import { FindPathTemplate, RequestPathAsyncTemplate } from '@esengine/pathfinding/nodes';
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);
}
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);
interface IPathResult {
readonly found: boolean; // Path found
readonly path: readonly IPoint[];// Path points
readonly cost: number; // Total cost
readonly nodesSearched: number; // Nodes searched
}
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)
}
FunctionUse CaseDescription
manhattanDistance4-directionalManhattan distance
euclideanDistanceAny directionEuclidean distance
chebyshevDistance8-directionalDiagonal cost = 1
octileDistance8-directionalDiagonal cost = √2 (default)
function createGridMap(
width: number,
height: number,
options?: IGridMapOptions
): GridMap

Options:

PropertyTypeDefaultDescription
allowDiagonalbooleantrueAllow diagonal movement
diagonalCostnumber√2Diagonal movement cost
avoidCornersbooleantrueAvoid corner cutting
heuristicHeuristicFunctionoctileDistanceHeuristic function
// 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();
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);
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);

Remove unnecessary waypoints:

import { createLineOfSightSmoother } from '@esengine/pathfinding';
const smoother = createLineOfSightSmoother();
const smoothedPath = smoother.smooth(result.path, grid);

Catmull-Rom spline:

import { createCatmullRomSmoother } from '@esengine/pathfinding';
const smoother = createCatmullRomSmoother(5, 0.5);
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';
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);
const hasLOS2 = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);
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);
}
}
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);
}
}
  • 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
  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
FeatureGridMapNavMesh
Use CaseRegular tile mapsComplex polygon terrain
MemoryHigher (width × height)Lower (polygon count)
PrecisionGrid-alignedContinuous coordinates
Dynamic UpdatesEasyRequires rebuild
Setup ComplexitySimpleMore complex