ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
163 lines (162 loc) • 6.45 kB
TypeScript
/**
* Pathfinding Plugin for ECSpresso
*
* A* pathfinding on a weighted grid. Produces waypoint lists consumed by the
* steering plugin — the pathfinding system writes the `path` component and
* sets `moveTarget` to the first waypoint; the waypoint advancement handler
* listens for `arriveAtTarget` and advances to the next waypoint.
*
* Exports the pure `findPath(grid, start, goal, options?)` function for
* turn-based / non-realtime consumers that don't need the component dance.
*/
import { type BasePluginOptions } from 'ecspresso';
import type { ComponentsConfig, EventsConfig, ResourcesConfig } from 'ecspresso';
import type { Vector2D } from '../../utils/math';
import type { TransformWorldConfig } from '../spatial/transform';
/** Flat-indexed cell position in a `NavGrid`. Transparent alias, not branded. */
export type CellIndex = number;
/**
* Grid topology. v1 ships `square4`; other values are accepted at construction
* but throw at `findPath` time.
*/
export type NavGridTopology = 'square4' | 'square8' | 'hex-pointy' | 'hex-flat';
/**
* Weighted navigation grid. Row-major storage (`idx = row * width + col`).
* Cell value `0` = impassable, `1`–`255` = traversal cost into that cell.
*/
export interface NavGrid {
readonly topology: NavGridTopology;
readonly width: number;
readonly height: number;
readonly cellSize: number;
readonly originX: number;
readonly originY: number;
readonly cells: Uint8Array;
worldToCell(wx: number, wy: number): CellIndex;
cellToWorld(idx: CellIndex): Vector2D;
cellFromXY(x: number, y: number): CellIndex;
cellToXY(idx: CellIndex): {
x: number;
y: number;
};
}
/** Options accepted by `createNavGrid`. */
export interface CreateNavGridOptions {
topology?: NavGridTopology;
width: number;
height: number;
cellSize?: number;
originX?: number;
originY?: number;
cells?: Uint8Array;
defaultCost?: number;
}
/** Signals the pathfinding system to compute a route to `target`. */
export interface PathRequest {
target: Vector2D;
}
/** Active route; waypoints are in world-space, advanced by `currentIndex`. */
export interface Path {
waypoints: Vector2D[];
currentIndex: number;
}
/** Component types provided by the pathfinding plugin. */
export interface PathfindingComponentTypes {
pathRequest: PathRequest;
path: Path;
}
/** Fired when A* produces a route. `path` is empty when start is already at the goal. */
export interface PathFoundEvent {
entityId: number;
path: Vector2D[];
}
/** Fired when no path exists to the target. */
export interface PathBlockedEvent {
entityId: number;
}
/** Event types provided by the pathfinding plugin. */
export interface PathfindingEventTypes {
pathFound: PathFoundEvent;
pathBlocked: PathBlockedEvent;
}
/** Resource types provided by the pathfinding plugin. */
export interface PathfindingResourceTypes {
navGrid: NavGrid;
}
/** WorldConfig representing the pathfinding plugin's provided types. */
export type PathfindingWorldConfig = ComponentsConfig<PathfindingComponentTypes> & EventsConfig<PathfindingEventTypes> & ResourcesConfig<PathfindingResourceTypes>;
export interface PathfindingPluginOptions<G extends string = 'ai'> extends BasePluginOptions<G> {
/** The navigation grid. Construct via `createNavGrid`. */
grid: NavGrid;
/** Max path requests processed per frame (default 4). */
maxRequestsPerFrame?: number;
/** Default `maxNodesExpanded` passed to A* per request (default 10_000). */
maxNodesExpanded?: number;
}
/**
* Create a weighted navigation grid.
*
* @example
* ```typescript
* const grid = createNavGrid({ width: 32, height: 32, cellSize: 16 });
* grid.cells[grid.cellFromXY(5, 5)] = 0; // block a cell
* ```
*/
export declare function createNavGrid(options: CreateNavGridOptions): NavGrid;
/**
* Create a `pathRequest` component for spreading into `spawn()` / `addComponent()`.
*
* @example
* ```typescript
* ecs.spawn({
* ...createTransform(0, 0),
* ...createMoveSpeed(100),
* ...createPathRequest({ x: 200, y: 300 }),
* });
* ```
*/
export declare function createPathRequest(target: Vector2D): Pick<PathfindingComponentTypes, 'pathRequest'>;
export interface FindPathOptions {
/** Cap on A* node expansions; returns `null` if exceeded. Default 10_000. */
maxNodesExpanded?: number;
/** Dynamic per-call obstacles layered on top of the static grid. */
blockedCells?: Set<CellIndex>;
/** Accept arrival within N cells of goal (topology-aware distance). Default 0. */
goalTolerance?: number;
}
/**
* Compute a path through `grid` from `start` to `goal`.
*
* Returns a list of cell indices starting with `start` and ending at a cell
* within `goalTolerance` of `goal`, or `null` if no such path exists within
* `maxNodesExpanded` expansions.
*
* `start` is always treated as passable (even if its grid cell is 0 or the
* cell is in `blockedCells`) — actors physics-pushed onto a wall still get a
* valid origin.
*/
export declare function findPath(grid: NavGrid, start: CellIndex, goal: CellIndex, options?: FindPathOptions): CellIndex[] | null;
/**
* Create a pathfinding plugin for ECSpresso.
*
* Requires the transform and steering plugins to be installed (entities need
* `worldTransform` for start-cell detection and `moveTarget`/`moveSpeed` for
* waypoint traversal).
*
* @example
* ```typescript
* const grid = createNavGrid({ width: 32, height: 32, cellSize: 16 });
* const ecs = ECSpresso.create()
* .withPlugin(createTransformPlugin())
* .withPlugin(createSteeringPlugin())
* .withPlugin(createPathfindingPlugin({ grid }))
* .build();
*
* ecs.spawn({
* ...createTransform(0, 0),
* ...createMoveSpeed(100),
* ...createPathRequest({ x: 500, y: 300 }),
* });
* ```
*/
export declare function createPathfindingPlugin<G extends string = 'ai'>(options: PathfindingPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithEvents<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, PathfindingComponentTypes>, PathfindingEventTypes>, PathfindingResourceTypes>, TransformWorldConfig & ComponentsConfig<import("../physics/steering").SteeringComponentTypes> & EventsConfig<import("../physics/steering").SteeringEventTypes>, "pathfinding-request" | "pathfinding-waypoint-advance", G, never, never>;