UNPKG

ecspresso

Version:

A minimal Entity-Component-System library for typescript and javascript.

163 lines (162 loc) 6.45 kB
/** * 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>;