player movement

This commit is contained in:
Elizabeth Hunt 2024-03-01 21:29:40 -07:00
parent d08e0105cb
commit c3242b171c
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
11 changed files with 290 additions and 35 deletions

View File

@ -1,7 +1,5 @@
import { GameCanvas } from "./components/GameCanvas"; import { GameCanvas } from "./components/GameCanvas";
import { Miscellaneous } from "./engine/config";
const WIDTH = 800;
const HEIGHT = 800;
export const App = () => { export const App = () => {
return ( return (
@ -12,7 +10,7 @@ export const App = () => {
</div> </div>
</div> </div>
<div className="content"> <div className="content">
<GameCanvas width={WIDTH} height={HEIGHT} /> <GameCanvas width={Miscellaneous.WIDTH} height={Miscellaneous.HEIGHT} />
</div> </div>
<div className="footer"> <div className="footer">
<span> <span>

View File

@ -1,7 +1,8 @@
import { Game } from "."; import { Game } from ".";
import { loadAssets } from "./config"; import { Miscellaneous, loadAssets } from "./config";
import { Player } from "./entities"; import { Player } from "./entities";
import { FacingDirection, Input, Render } from "./systems"; import { FacingDirection, Input, Render } from "./systems";
import { Grid } from "./systems/Grid";
export class TheAbstractionEngine { export class TheAbstractionEngine {
private game: Game; private game: Game;
@ -22,9 +23,18 @@ export class TheAbstractionEngine {
const facingDirectionSystem = new FacingDirection(inputSystem); const facingDirectionSystem = new FacingDirection(inputSystem);
[new Render(this.ctx), inputSystem, facingDirectionSystem].forEach( [
(system) => this.game.addSystem(system), new Render(this.ctx),
); inputSystem,
facingDirectionSystem,
new Grid(
{ width: Miscellaneous.GRID_COLUMNS, height: Miscellaneous.GRID_ROWS },
{
width: Miscellaneous.GRID_CELL_WIDTH,
height: Miscellaneous.GRID_CELL_HEIGHT,
},
),
].forEach((system) => this.game.addSystem(system));
const player = new Player(); const player = new Player();
this.game.addEntity(player); this.game.addEntity(player);

View File

@ -1,7 +1,7 @@
export namespace ComponentNames { export namespace ComponentNames {
export const Sprite = "Sprite"; export const Sprite = "Sprite";
export const FacingDirection = "FacingDirection"; export const FacingDirection = "FacingDirection";
export const GridPosition = "GridPosition"; export const Grid = "Grid";
export const BoundingBox = "BoundingBox"; export const BoundingBox = "BoundingBox";
export const Control = "Control"; export const Control = "Control";
} }

View File

@ -0,0 +1,20 @@
import { Component, ComponentNames } from ".";
import { Coord2D, Direction } from "../interfaces";
export class Grid extends Component {
public initialized: boolean;
public gridPosition: Coord2D;
public movingDirection: Direction;
public pushable: boolean = false;
constructor(pushable: boolean = false, position: Coord2D = { x: 0, y: 0 }) {
super(ComponentNames.Grid);
this.initialized = false;
this.gridPosition = position;
this.movingDirection = Direction.NONE;
this.pushable = pushable;
}
}

View File

@ -1,13 +0,0 @@
import { Component, ComponentNames } from ".";
export class GridPosition extends Component {
public x: number;
public y: number;
constructor(x: number, y: number) {
super(ComponentNames.GridPosition);
this.x = x;
this.y = y;
}
}

View File

@ -2,6 +2,6 @@ export * from "./Component";
export * from "./ComponentNames"; export * from "./ComponentNames";
export * from "./Sprite"; export * from "./Sprite";
export * from "./FacingDirection"; export * from "./FacingDirection";
export * from "./GridPosition"; export * from "./Grid";
export * from "./BoundingBox"; export * from "./BoundingBox";
export * from "./Control"; export * from "./Control";

View File

@ -41,10 +41,17 @@ export namespace KeyConstants {
}, new Map()); }, new Map());
} }
export namespace PhysicsConstants {
export const GRID_MOVEMENT_VELOCITY = 2;
}
export namespace Miscellaneous { export namespace Miscellaneous {
export const WIDTH = 800; export const WIDTH = 800;
export const HEIGHT = 800; export const HEIGHT = 800;
export const DEFAULT_GRID_WIDTH = 30; export const GRID_ROWS = 15;
export const DEFAULT_GRID_HEIGHT = 30; export const GRID_COLUMNS = 15;
export const GRID_CELL_WIDTH = Math.floor(WIDTH / GRID_COLUMNS);
export const GRID_CELL_HEIGHT = Math.floor(HEIGHT / GRID_ROWS);
} }

View File

@ -3,7 +3,7 @@ import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config";
import { import {
FacingDirection, FacingDirection,
Sprite, Sprite,
GridPosition, Grid,
BoundingBox, BoundingBox,
Control, Control,
} from "../components"; } from "../components";
@ -20,8 +20,8 @@ export class Player extends Entity {
this.addComponent( this.addComponent(
new BoundingBox( new BoundingBox(
{ {
x: 200, x: 0,
y: 200, y: 0,
}, },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height }, { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
0, 0,
@ -30,7 +30,7 @@ export class Player extends Entity {
this.addComponent(new Control()); this.addComponent(new Control());
this.addComponent(new GridPosition(0, 0)); this.addComponent(new Grid());
this.addFacingDirectionComponents(); this.addFacingDirectionComponents();
} }

199
src/engine/systems/Grid.ts Normal file
View File

@ -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);
});
}
}

View File

@ -4,7 +4,7 @@ import { ComponentNames } from "../components";
import { Control } from "../components/Control"; import { Control } from "../components/Control";
import { Action, KeyConstants } from "../config"; import { Action, KeyConstants } from "../config";
import { Entity } from "../entities"; import { Entity } from "../entities";
import { Coord2D } from "../interfaces"; import { Coord2D, Direction } from "../interfaces";
export class Input extends System { export class Input extends System {
private keys: Set<string>; private keys: Set<string>;
@ -41,8 +41,44 @@ export class Input extends System {
); );
if (!controlComponent.isControllable) return; if (!controlComponent.isControllable) return;
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.INTERACT))) { const hasGrid = entity.hasComponent(ComponentNames.Grid);
console.log("interact");
// TODO: check grid via controlComponent to notify entity of interaction
const [moveUp, moveLeft, moveRight, moveDown] = [
Action.MOVE_UP,
Action.MOVE_LEFT,
Action.MOVE_RIGHT,
Action.MOVE_DOWN,
].map((action) => this.hasSomeKey(KeyConstants.ActionKeys.get(action)));
if (hasGrid) {
const gridComponent = entity.getComponent(ComponentNames.Grid);
if (gridComponent.movingDirection !== Direction.NONE) {
return;
}
if (moveUp) {
gridComponent.movingDirection = Direction.UP;
KeyConstants.ActionKeys.get(Action.MOVE_UP)!.forEach((key) =>
this.keyReleased(key),
);
} else if (moveLeft) {
gridComponent.movingDirection = Direction.LEFT;
KeyConstants.ActionKeys.get(Action.MOVE_LEFT)!.forEach((key) =>
this.keyReleased(key),
);
} else if (moveRight) {
gridComponent.movingDirection = Direction.RIGHT;
KeyConstants.ActionKeys.get(Action.MOVE_RIGHT)!.forEach((key) =>
this.keyReleased(key),
);
} else if (moveDown) {
gridComponent.movingDirection = Direction.DOWN;
KeyConstants.ActionKeys.get(Action.MOVE_DOWN)!.forEach((key) =>
this.keyReleased(key),
);
}
entity.addComponent(gridComponent);
} }
} }

View File

@ -1,8 +1,6 @@
export namespace SystemNames { export namespace SystemNames {
export const Render = "Render"; export const Render = "Render";
export const Physics = "Physics";
export const Input = "Input"; export const Input = "Input";
export const Collision = "Collision";
export const WallBounds = "WallBounds";
export const FacingDirection = "FacingDirection"; export const FacingDirection = "FacingDirection";
export const Grid = "Grid";
} }