201 lines
5.6 KiB
TypeScript
Raw Normal View History

2024-03-01 21:29:40 -07:00
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()));
}
2024-03-01 22:04:57 -07:00
public update(dt: number, game: Game) {
2024-03-01 21:29:40 -07:00
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);
2024-03-01 22:04:57 -07:00
boundingBox.dimension = this.dimension;
2024-03-01 21:29:40 -07:00
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);
});
}
}