Spatial Grid
The spatialGrid resource maps cells to the entities occupying them. SpatialPlugin installs it for
navigation, but it is also useful on its own for coarse proximity queries.
Use the grid without navigation
Section titled “Use the grid without navigation”Install GridPlugin when you only need the index:
import { GridPlugin } from '@shell/spatial';
world.addPlugin( GridPlugin.config({ cellSize: 64, width: 2048, height: 2048, }),);Add both Position and GridCell to every entity that should be indexed:
import { Define, Position } from '@shell/engine-2d';import { GridCell } from '@shell/spatial';
const IndexedEntity = Define.prefab().with(Position).with(GridCell);The plugin synchronizes GridCell and rebuilds the index during FixedPreUpdate.
Query one cell
Section titled “Query one cell”Convert a world position to cell coordinates, then inspect that cell:
const grid = resources.get('spatialGrid');const [cellX, cellY] = grid.worldToCell(position.x, position.y);const entityIndices = grid.getEntitiesInCell(cellX, cellY);The returned values are compact entity indices. Resolve them through the current world’s entities
resource when an Entity object is needed:
for (const index of entityIndices) { const entity = resources.get('entities').getByIndex(index); if (!entity) continue; // Inspect the candidate entity.}Entity indices can be reused after despawn. Resolve and use grid results within the current spatial snapshot rather than retaining them as durable identifiers.
Query neighboring cells
Section titled “Query neighboring cells”getNeighborEntities() includes the center cell and an extent measured in cells:
const nearby: number[] = [];
grid.getNeighborEntities(cellX, cellY, nearby); // 3 x 3 cellsgrid.getNeighborEntities(cellX, cellY, nearby, 2); // 5 x 5 cellsPass a reusable output array in update loops. The method clears its logical contents before filling it and returns the same array.
Query world bounds
Section titled “Query world bounds”Use getEntitiesInWorldBounds() when the search area is expressed in world units:
const candidates: number[] = [];
grid.getEntitiesInWorldBounds( position.x - 100, position.y - 100, position.x + 100, position.y + 100, candidates,);Maximum bounds are exclusive. Bounds are clipped to the configured world, and a rectangle entirely outside it returns an empty array.
Grid queries return broad-phase candidates from overlapping cells. Apply an exact distance, shape, component, or gameplay test after the query when false positives matter.
Timing and bounds
Section titled “Timing and bounds”The grid is a fixed-step snapshot. Changes to Position made after FixedPreUpdate appear in the
index on the next fixed step. Schedule systems accordingly when they require freshly synchronized
membership.
worldToCell() clamps finite coordinates to the edge of the configured grid. Cell-based methods
instead require integer, in-bounds coordinates and report invalid input. The resource exposes
cellSize, width, height, cols, rows, and totalCells for bounds-aware gameplay.
Standalone plugin composition
Section titled “Standalone plugin composition”The package also exports FlowfieldPlugin, ObstaclePlugin, LineOfSightPlugin, and
SeparationPlugin. Their dependencies install the grid automatically, but plugin configuration is
first-install-wins. Install a configured GridPlugin before dependent standalone plugins when you
need non-default dimensions:
import { FlowfieldPlugin, GridPlugin, SeparationPlugin } from '@shell/spatial';
world.addPlugin( GridPlugin.config({ cellSize: 32, width: 1024, height: 1024 }), FlowfieldPlugin, SeparationPlugin,);For the complete feature set, configure SpatialPlugin instead.