diff --git a/public/assets/function_block.png b/public/assets/function_block.png new file mode 100644 index 0000000..a54a4a0 Binary files /dev/null and b/public/assets/function_block.png differ diff --git a/public/assets/function_factory.png b/public/assets/function_factory.png new file mode 100644 index 0000000..2c46758 Binary files /dev/null and b/public/assets/function_factory.png differ diff --git a/public/assets/key.png b/public/assets/key.png new file mode 100644 index 0000000..c6d07a4 Binary files /dev/null and b/public/assets/key.png differ diff --git a/public/assets/locked_door.png b/public/assets/locked_door.png new file mode 100644 index 0000000..4a90d84 Binary files /dev/null and b/public/assets/locked_door.png differ diff --git a/public/assets/wall.png b/public/assets/wall.png new file mode 100644 index 0000000..fe9390d Binary files /dev/null and b/public/assets/wall.png differ diff --git a/src/components/GameCanvas.tsx b/src/components/GameCanvas.tsx index 5cb40a6..0ea7180 100644 --- a/src/components/GameCanvas.tsx +++ b/src/components/GameCanvas.tsx @@ -8,10 +8,10 @@ export interface GameCanvasProps { export const GameCanvas = ({ width, height }: GameCanvasProps) => { const canvasRef = useRef(null); - const [_game, setGame] = useState(); + const [game, setGame] = useState(); useEffect(() => { - if (canvasRef.current) { + if (canvasRef.current && !game) { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); if (ctx) { diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts index 3859447..78d4f88 100644 --- a/src/engine/TheAbstractionEngine.ts +++ b/src/engine/TheAbstractionEngine.ts @@ -39,8 +39,10 @@ export class TheAbstractionEngine { const player = new Player(); this.game.addEntity(player); - const box = new FunctionBox({ x: 5, y: 5 }); + const box = new FunctionBox({ x: 3, y: 1 }); this.game.addEntity(box); + const box2 = new FunctionBox({ x: 4, y: 1 }); + this.game.addEntity(box2); } public play() { diff --git a/src/engine/components/ComponentNames.ts b/src/engine/components/ComponentNames.ts index 032d08a..da6c37a 100644 --- a/src/engine/components/ComponentNames.ts +++ b/src/engine/components/ComponentNames.ts @@ -4,4 +4,6 @@ export namespace ComponentNames { export const Grid = "Grid"; export const BoundingBox = "BoundingBox"; export const Control = "Control"; + export const Highlight = "Highlight"; + export const Interactable = "Interactable"; } diff --git a/src/engine/components/Highlight.ts b/src/engine/components/Highlight.ts new file mode 100644 index 0000000..49d9f96 --- /dev/null +++ b/src/engine/components/Highlight.ts @@ -0,0 +1,7 @@ +import { Component, ComponentNames } from "."; + +export class Highlight extends Component { + constructor() { + super(ComponentNames.Highlight); + } +} diff --git a/src/engine/components/Interactable.ts b/src/engine/components/Interactable.ts new file mode 100644 index 0000000..2937596 --- /dev/null +++ b/src/engine/components/Interactable.ts @@ -0,0 +1,15 @@ +import { Component, ComponentNames } from "."; + +export class Interactable extends Component { + private interaction: Function; + + constructor(interaction: Function) { + super(ComponentNames.Interactable); + + this.interaction = interaction; + } + + public interact() { + this.interaction(); + } +} diff --git a/src/engine/components/index.ts b/src/engine/components/index.ts index e9f8de9..c470eff 100644 --- a/src/engine/components/index.ts +++ b/src/engine/components/index.ts @@ -5,3 +5,5 @@ export * from "./FacingDirection"; export * from "./Grid"; export * from "./BoundingBox"; export * from "./Control"; +export * from "./Highlight"; +export * from "./Interactable"; diff --git a/src/engine/config/constants.ts b/src/engine/config/constants.ts index 0b07108..5dcd60c 100644 --- a/src/engine/config/constants.ts +++ b/src/engine/config/constants.ts @@ -42,7 +42,7 @@ export namespace KeyConstants { } export namespace PhysicsConstants { - export const GRID_MOVEMENT_VELOCITY = 2; + export const GRID_MOVEMENT_VELOCITY = 1; } export namespace Miscellaneous { diff --git a/src/engine/config/sprites.ts b/src/engine/config/sprites.ts index e62d714..eab65fd 100644 --- a/src/engine/config/sprites.ts +++ b/src/engine/config/sprites.ts @@ -43,6 +43,6 @@ const functionBoxSpriteSpec = { width: 64, height: 64, frames: 3, - sheet: "/assets/border.png", + sheet: "/assets/function_block.png", }; SPRITE_SPECS.set(Sprites.FUNCTION_BOX, functionBoxSpriteSpec); diff --git a/src/engine/entities/Entity.ts b/src/engine/entities/Entity.ts index 2cc2ac3..d5a8e6e 100644 --- a/src/engine/entities/Entity.ts +++ b/src/engine/entities/Entity.ts @@ -7,14 +7,26 @@ export abstract class Entity { public components: Map; public name: string; + protected hooks: Map; + constructor(name: string, id: string = (Entity.Id++).toString()) { this.name = name; this.id = id; this.components = new Map(); + this.hooks = new Map(); } public addComponent(component: Component) { + const hadBeforeSet = this.components.has(component.name); this.components.set(component.name, component); + if (!hadBeforeSet) { + this.hooks.get(component.name)?.add(); + } + } + + public removeComponent(name: string) { + this.components.delete(name); + this.hooks.get(name)?.remove(); } public getComponent(name: string): T { diff --git a/src/engine/entities/FunctionBox.ts b/src/engine/entities/FunctionBox.ts index e6c41c2..393514e 100644 --- a/src/engine/entities/FunctionBox.ts +++ b/src/engine/entities/FunctionBox.ts @@ -1,6 +1,12 @@ import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; import { Entity, EntityNames } from "."; -import { BoundingBox, Grid, Sprite } from "../components"; +import { + BoundingBox, + ComponentNames, + Grid, + Interactable, + Sprite, +} from "../components"; import { Coord2D } from "../interfaces"; export class FunctionBox extends Entity { @@ -8,9 +14,13 @@ export class FunctionBox extends Entity { Sprites.FUNCTION_BOX, ) as SpriteSpec; - constructor(gridPosition: Coord2D) { + private code: string; + + constructor(gridPosition: Coord2D, code: string) { super(EntityNames.FunctionBox); + this.code = code; + this.addComponent( new BoundingBox( { @@ -39,5 +49,18 @@ export class FunctionBox extends Entity { FunctionBox.spriteSpec.frames, ), ); + + this.hooks.set(ComponentNames.Highlight, { + add: () => { + this.addComponent(new Interactable(() => this.viewInsides())); + }, + remove: () => { + this.removeComponent(ComponentNames.Interactable); + }, + }); + } + + public viewInsides() { + console.log("I am a function box!"); } } diff --git a/src/engine/systems/FacingDirection.ts b/src/engine/systems/FacingDirection.ts index f831bf6..042484a 100644 --- a/src/engine/systems/FacingDirection.ts +++ b/src/engine/systems/FacingDirection.ts @@ -51,6 +51,7 @@ export class FacingDirection extends System { : angleToDirection(angle); facingDirection.setDirection(direction); + entity.addComponent(facingDirection); const oldSprite = entity.getComponent(ComponentNames.Sprite); const sprite = facingDirection.directionSprites.get(direction)!; diff --git a/src/engine/systems/Grid.ts b/src/engine/systems/Grid.ts index 0869fd6..c9cab6b 100644 --- a/src/engine/systems/Grid.ts +++ b/src/engine/systems/Grid.ts @@ -1,9 +1,12 @@ import { System, SystemNames } from "."; import { Game } from ".."; +import { Entity } from "../entities"; import { PhysicsConstants } from "../config"; import { BoundingBox, ComponentNames, + FacingDirection, + Highlight, Grid as GridComponent, } from "../components"; import { Coord2D, Direction, Dimension2D } from "../interfaces"; @@ -28,9 +31,109 @@ export class Grid extends System { public update(dt: number, game: Game) { this.putUninitializedEntitiesInGrid(game); this.rebuildGrid(game); + + this.highlightEntitiesLookedAt(game); + this.propogateEntityMovements(game); + this.updateMovingEntities(dt, game); } + private highlightEntitiesLookedAt(game: Game) { + const highlightableEntities = new Set(); + + game.forEachEntityWithComponent( + ComponentNames.FacingDirection, + (entity) => { + if (!entity.hasComponent(ComponentNames.Grid)) { + return; + } + + const grid = entity.getComponent(ComponentNames.Grid)!; + const facingDirection = entity.getComponent( + ComponentNames.FacingDirection, + )!; + const lookingAt = this.getNewGridPosition( + grid.gridPosition, + facingDirection.currentDirection, + ); + if ( + facingDirection.currentDirection === Direction.NONE || + this.isOutOfBounds(lookingAt) + ) { + return; + } + + this.grid[lookingAt.y][lookingAt.x].forEach((id) => { + highlightableEntities.add(id); + }); + }, + ); + + highlightableEntities.forEach((id) => { + const entity = game.getEntity(id)!; + if (!entity.hasComponent(ComponentNames.Highlight)) { + entity.addComponent(new Highlight()); + } + }); + + game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => { + if (!highlightableEntities.has(entity.id)) { + entity.removeComponent(ComponentNames.Highlight); + } + }); + } + + private propogateEntityMovements(game: Game) { + const movingEntities: Entity[] = []; + game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => { + const grid = entity.getComponent(ComponentNames.Grid)!; + if (grid.movingDirection !== Direction.NONE) { + movingEntities.push(entity); + } + }); + + // for each moving entity, check the entities in the grid cell it's moving to + // if they are pushable, move them in the same direction + // continue until no more pushable entities are found + for (const entity of movingEntities) { + const grid = entity.getComponent(ComponentNames.Grid)!; + let nextGridPosition = this.getNewGridPosition( + grid.gridPosition, + grid.movingDirection, + ); + while (!this.isOutOfBounds(nextGridPosition)) { + const { x, y } = nextGridPosition; + const entities = Array.from(this.grid[y][x]).map( + (id) => game.getEntity(id)!, + ); + + const pushableEntities = entities.filter((entity) => { + if (!entity.hasComponent(ComponentNames.Grid)) return false; + + const { pushable, movingDirection } = + entity.getComponent(ComponentNames.Grid)!; + return movingDirection === Direction.NONE && pushable; + }); + if (pushableEntities.length === 0) { + break; + } + + for (const pushableEntity of pushableEntities) { + const pushableGrid = pushableEntity.getComponent( + ComponentNames.Grid, + )!; + pushableGrid.movingDirection = grid.movingDirection; + pushableEntity.addComponent(pushableEntity); + } + + nextGridPosition = this.getNewGridPosition( + nextGridPosition, + grid.movingDirection, + ); + } + } + } + private putUninitializedEntitiesInGrid(game: Game) { game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => { const grid = entity.getComponent(ComponentNames.Grid)!; @@ -71,23 +174,10 @@ export class Grid extends System { 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 }; + const newGridPosition = this.getNewGridPosition( + grid.gridPosition, + grid.movingDirection, + ); if (this.isOutOfBounds(newGridPosition)) { grid.movingDirection = Direction.NONE; entity.addComponent(grid); @@ -137,6 +227,26 @@ export class Grid extends System { }); } + private getNewGridPosition(prev: Coord2D, direction: Direction) { + let { x: newX, y: newY } = prev; + switch (direction) { + case Direction.LEFT: + newX -= 1; + break; + case Direction.UP: + newY -= 1; + break; + case Direction.DOWN: + newY += 1; + break; + case Direction.RIGHT: + newX += 1; + break; + } + + return { x: newX, y: newY }; + } + private isEntityPastCenterWhenMoving( direction: Direction, gridPosition: Coord2D, @@ -185,7 +295,7 @@ export class Grid extends System { this.grid.forEach((row) => row.forEach((cell) => { for (const id of cell) { - if (!movedEntities.has(id)) { + if (movedEntities.has(id)) { cell.delete(id); } } @@ -194,7 +304,8 @@ export class Grid extends System { movedEntities.forEach((id) => { const entity = game.getEntity(id)!; const grid = entity.getComponent(ComponentNames.Grid)!; - this.grid[grid.gridPosition.y][grid.gridPosition.x].add(id); + const { x, y } = grid.gridPosition; + this.grid[y][x].add(id); }); } } diff --git a/src/engine/systems/Input.ts b/src/engine/systems/Input.ts index e9691e0..df4d651 100644 --- a/src/engine/systems/Input.ts +++ b/src/engine/systems/Input.ts @@ -1,6 +1,6 @@ import { SystemNames, System } from "."; import { Game } from ".."; -import { ComponentNames } from "../components"; +import { ComponentNames, Grid, Interactable } from "../components"; import { Control } from "../components/Control"; import { Action, KeyConstants } from "../config"; import { Entity } from "../entities"; @@ -31,11 +31,30 @@ export class Input extends System { public update(_dt: number, game: Game) { game.forEachEntityWithComponent(ComponentNames.Control, (entity) => - this.handleInput(entity), + this.handleMovement(entity), + ); + game.forEachEntityWithComponent(ComponentNames.Interactable, (entity) => + this.handleInteraction(entity), ); } - public handleInput(entity: Entity) { + private handleInteraction(entity: Entity) { + const interactable = entity.getComponent( + ComponentNames.Interactable, + ); + + const interact = this.hasSomeKey( + KeyConstants.ActionKeys.get(Action.INTERACT), + ); + + if (!interact) { + return; + } + + interactable.interact(); + } + + public handleMovement(entity: Entity) { const controlComponent = entity.getComponent( ComponentNames.Control, ); @@ -50,36 +69,38 @@ export class Input extends System { 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); + if (!hasGrid) { + return; } + + 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); } private hasSomeKey(keys?: string[]): boolean { diff --git a/src/engine/systems/Render.ts b/src/engine/systems/Render.ts index 6f539c0..7cb5d81 100644 --- a/src/engine/systems/Render.ts +++ b/src/engine/systems/Render.ts @@ -2,6 +2,7 @@ import { System, SystemNames } from "."; import { BoundingBox, ComponentNames, Sprite } from "../components"; import { Game } from ".."; import { clamp } from "../utils"; +import { DrawArgs } from "../interfaces"; export class Render extends System { private ctx: CanvasRenderingContext2D; @@ -38,10 +39,11 @@ export class Render extends System { return; } - const drawArgs = { + const drawArgs: DrawArgs = { center: boundingBox.center, dimension: boundingBox.dimension, rotation: boundingBox.rotation, + tint: entity.hasComponent(ComponentNames.Highlight) ? "red" : undefined, }; sprite.draw(this.ctx, drawArgs); diff --git a/src/main.tsx b/src/main.tsx index 94b1039..7404467 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,9 +1,4 @@ -import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App.tsx"; import "./css/style.css"; -ReactDOM.createRoot(document.getElementById("root")!).render( - - - , -); +ReactDOM.createRoot(document.getElementById("root")!).render();