diff options
Diffstat (limited to 'src/engine/systems/Grid.ts')
-rw-r--r-- | src/engine/systems/Grid.ts | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/src/engine/systems/Grid.ts b/src/engine/systems/Grid.ts new file mode 100644 index 0000000..e4e4bb8 --- /dev/null +++ b/src/engine/systems/Grid.ts @@ -0,0 +1,199 @@ +import { System, SystemNames } from "."; +import { Game } from ".."; +import { PhysicsConstants } from "../config"; +import { + BoundingBox, + ComponentNames, + Grid as GridComponent, +} from "../components"; +import { Coord2D, Direction, Dimension2D } from "../interfaces"; +import { clamp } from "../utils"; + +export class Grid extends System { + private dimension: Dimension2D; + private grid: Set<string>[][] = []; + + constructor( + { width: columns, height: rows }: Dimension2D, + dimension: Dimension2D, + ) { + super(SystemNames.Grid); + + this.dimension = dimension; + this.grid = new Array(rows) + .fill(null) + .map(() => new Array(columns).fill(null).map(() => new Set())); + } + + public update(dt: number, game: Game): void { + this.putUninitializedEntitiesInGrid(game); + this.rebuildGrid(game); + this.updateMovingEntities(dt, game); + } + + private putUninitializedEntitiesInGrid(game: Game) { + game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => { + const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!; + + if (grid.initialized) { + return; + } + + const hasBoundingBox = entity.hasComponent(ComponentNames.BoundingBox); + if (!hasBoundingBox) { + return; + } + + const boundingBox = entity.getComponent<BoundingBox>( + ComponentNames.BoundingBox, + )!; + boundingBox.center = this.gridToScreenPosition(grid.gridPosition); + entity.addComponent(boundingBox); + + grid.initialized = true; + entity.addComponent(grid); + }); + } + + private updateMovingEntities( + dt: number, + game: Game, + velocity = PhysicsConstants.GRID_MOVEMENT_VELOCITY, + ) { + game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => { + const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!; + if (grid.movingDirection === Direction.NONE) { + return; + } + + const boundingBox = entity.getComponent<BoundingBox>( + ComponentNames.BoundingBox, + )!; + + let { x: newX, y: newY } = grid.gridPosition; + switch (grid.movingDirection) { + case Direction.LEFT: + newX -= 1; + break; + case Direction.UP: + newY -= 1; + break; + case Direction.DOWN: + newY += 1; + break; + case Direction.RIGHT: + newX += 1; + break; + } + + const newGridPosition = { x: newX, y: newY }; + if (this.isOutOfBounds(newGridPosition)) { + grid.movingDirection = Direction.NONE; + entity.addComponent(grid); + return; + } + + let { dx, dy } = { dx: 0, dy: 0 }; + switch (grid.movingDirection) { + case Direction.LEFT: + dx = -velocity * dt; + break; + case Direction.RIGHT: + dx = velocity * dt; + break; + case Direction.UP: + dy = -velocity * dt; + break; + case Direction.DOWN: + dy = velocity * dt; + break; + } + + const { x, y } = boundingBox.center; + const nextPosition = { x: x + dx, y: y + dy }; + const passedCenter = this.isEntityPastCenterWhenMoving( + grid.movingDirection, + newGridPosition, + nextPosition, + ); + + if (passedCenter) { + // re-align the entity to its new grid position + this.grid[grid.gridPosition.y][grid.gridPosition.x].delete(entity.id); + + grid.gridPosition = newGridPosition; + grid.movingDirection = Direction.NONE; + this.grid[grid.gridPosition.y][grid.gridPosition.x].add(entity.id); + entity.addComponent(grid); + + boundingBox.center = this.gridToScreenPosition(grid.gridPosition); + entity.addComponent(boundingBox); + return; + } + + boundingBox.center = nextPosition; + entity.addComponent(boundingBox); + }); + } + + private isEntityPastCenterWhenMoving( + direction: Direction, + gridPosition: Coord2D, + entityPosition: Coord2D, + ) { + const { x, y } = this.gridToScreenPosition(gridPosition); + switch (direction) { + case Direction.LEFT: + return entityPosition.x <= x; + case Direction.RIGHT: + return entityPosition.x >= x; + case Direction.UP: + return entityPosition.y <= y; + case Direction.DOWN: + return entityPosition.y >= y; + } + return false; + } + + private gridToScreenPosition(gridPosition: Coord2D) { + const { width, height } = this.dimension; + return { + x: gridPosition.x * width + width / 2, + y: gridPosition.y * height + height / 2, + }; + } + + private isOutOfBounds(position: Coord2D) { + const isOutOfBoundsX = + clamp(position.x, 0, this.grid[0].length - 1) !== position.x; + const isOutOfBoundsY = + clamp(position.y, 0, this.grid.length - 1) !== position.y; + return isOutOfBoundsX || isOutOfBoundsY; + } + + private rebuildGrid(game: Game) { + const movedEntities = new Set<string>(); + game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => { + const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!; + const { x, y } = grid.gridPosition; + if (!this.grid[y][x].has(entity.id)) { + movedEntities.add(entity.id); + this.grid[y][x].add(entity.id); + } + }); + this.grid.forEach((row) => + row.forEach((cell) => { + for (const id of cell) { + if (!movedEntities.has(id)) { + cell.delete(id); + } + } + }), + ); + movedEntities.forEach((id) => { + const entity = game.getEntity(id)!; + const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!; + this.grid[grid.gridPosition.y][grid.gridPosition.x].add(id); + }); + } +} |