From 1ec5a8d088f599d094f387abc6014f228607b605 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 2 Mar 2024 01:07:55 -0700 Subject: [PATCH] add interactable component --- public/assets/function_block.png | Bin 0 -> 1731 bytes public/assets/function_factory.png | Bin 0 -> 1302 bytes public/assets/key.png | Bin 0 -> 1086 bytes public/assets/locked_door.png | Bin 0 -> 1845 bytes public/assets/wall.png | Bin 0 -> 2247 bytes src/components/GameCanvas.tsx | 4 +- src/engine/TheAbstractionEngine.ts | 4 +- src/engine/components/ComponentNames.ts | 2 + src/engine/components/Highlight.ts | 7 ++ src/engine/components/Interactable.ts | 15 +++ src/engine/components/index.ts | 2 + src/engine/config/constants.ts | 2 +- src/engine/config/sprites.ts | 2 +- src/engine/entities/Entity.ts | 12 ++ src/engine/entities/FunctionBox.ts | 27 ++++- src/engine/systems/FacingDirection.ts | 1 + src/engine/systems/Grid.ts | 149 +++++++++++++++++++++--- src/engine/systems/Input.ts | 85 +++++++++----- src/engine/systems/Render.ts | 4 +- src/main.tsx | 7 +- 20 files changed, 258 insertions(+), 65 deletions(-) create mode 100644 public/assets/function_block.png create mode 100644 public/assets/function_factory.png create mode 100644 public/assets/key.png create mode 100644 public/assets/locked_door.png create mode 100644 public/assets/wall.png create mode 100644 src/engine/components/Highlight.ts create mode 100644 src/engine/components/Interactable.ts diff --git a/public/assets/function_block.png b/public/assets/function_block.png new file mode 100644 index 0000000000000000000000000000000000000000..a54a4a056a4fdac0a31c67f0c44926c2308c6a93 GIT binary patch literal 1731 zcmV;!20ZzRP)LkG|c{m=#wxB(wu$y(X^N{;zS9NW9zm1HM5_sxIczpr2Z z|G_nV_x}5v{@SQLIHcH&8dto@xq&zCYM8z8w#(&#{grStglwfr4}wN>i(Gt(qiF2M ze5&AI!Oj|>3OH%uN7q+~xpE*l=Dx9G${`JK2l4mtX9aO}E;4Z=V|()}%^{?AmnMOE z^-rr6!jO5a^gl9P-!C$kv%l3jRUHdi_f_oY7#s%=29O5R5Q%`ZXd}&;8iT_z)0afB z1xBt9&3w_d)AV^2?4x=9HGl*hCD3!hk-jAy92w|+*7Y@l32B4Of3$|k{h5N7I6&v$ za{w9OD&lWRLI^o=A#JM)U%OeK&3;$XfV7@1mfr@@p7Bd=taKg0#DL8sN&{k!&!^09VJZF}0>`rQjyb1qptv0j@3pBqH9~0He&_uI*jLj|M=}0CyAr zNd|QkNeSRi_$7SX0K^{PP68N3f&-@{5Gl=8zb*+p3;)|spUHR2zJ2^4zMeC~Img4! z0K`jv5dnY`z`3}VLKumh%8D}r)7&Fr_uzml2~!P_9R;WiP(l3G0=TLH4j#M%L5cy; z2;faXbpu!$fs6DNGQc77ktVE6Km~%78dzfhZxKWbpvT@WuBQkP14LxMV?A4(l(;D# zyi8ES04F$40Z=tyMg|xepchA5SFZ|yD}&nq-v)SL+?xm~0#HI&6+lG;NSGwEXf~c# zGQf!o(TRowN&~MlfVcRO6zD|&@*Kb$zh+S*4d9Kt6)MUB+D>DFUIXC_j(W}J;}`CVDy2AW%jFze;c5C^K%>EF^5={K$=%wB1=HX z0N#;-?~DD@dyh(Do^< z|Jk3fUHCkZ2$}G))_NuA3&`qY6j|FFAao!4^R=jX9+YD z1*NEFA%aW;tONuL?BpJRON$=qaFzfs@LAsk&jtev9#Fo4%PE@)+(brhV9dp`$wVIWHY)Co|9Iyfo=Yy-R~ATtAuGF{X@ zpn?!utWnQGve&&t2B^09?U84M0V?3{gFu7@(?{?qS|c(5UgB50JF~l zB;#k6K9L5Hei>rLRIB&4`@PT%kSTob6u|lzuwu#63I=^2YBmE@&3qgOZU$I&smJ{h z55g*@e9rv6$X8Fot{{GK4=|hhJp^mrXM_Q)=WN*@hkukEHRD&$e&jLes_QibWw+f1 zczhS$I|EcGd3()?KZ|nL3^0m*Qe@0FuGRPw&SwaQ+W?K4xQAe~8$c_edi0|e|Fduq z{wI<=7FX(%`wVa!;5++8;N1+sY2YbNfg}U;Cj+OU?o;rjx_)U$TK32CMuE3%}WB`_KS!_nuq1}Sw31WEbw178zTJuHUJm> z(8bi40+|##?n*mq&dA&}She#*#@F^aYTsj78Q+4JmGPzH+)*$eZh|ZcuhiE}#+aS| zXrn#b=jwg40bq69D)+be{~BO56I1~w&0`d;R-=$Kc7^L3|G$&3V?7=W&@WJyAdr+g zGdHh(y@hQhS_D6`FY?Cg(LaZMXaH^^Gve#}(k_MvDM+6dSdnqsHr2WD<^;iy&Oz$y ZCnr^JQY-mP`~Uy|07*qoM6N<$f&l*;2c!T1 literal 0 HcmV?d00001 diff --git a/public/assets/function_factory.png b/public/assets/function_factory.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4675803197cf921b40be3b99188e72c383cbd8 GIT binary patch literal 1302 zcmV+x1?l>UP) z)k8bzzZaQPd`tk~Q9wxRU_b(lc#&uT%usGH=~dW~)gMWVH-rPX_MMV+;fYfQkS}02o9c=E<01yhHSSJK6{n05Piu}nM07VJN9uVCDSWbQx0Z`Ha#3GOY7+3v6`*%_VGXk{# z4n$AAa#RPP?VJ&SMeubHh7kP;0Auot7X4%$BqzZHz-2VvQ>bpm3u+3W>i`14@QAI9 zt|z(#z&IEp00;o695~emmfew_oK;O;bSsoaFpJ=`4ItG)QdR(B&%vHp$J0kQUh!h0wVAesW9D1a%@Ed`Q* zKskQ`0DKonr-5=QuJ**aM|uPR0dOsX(b69o{fFOK1cSFh5dk1U0P)WetT2qe4XQmM zO8&3YKe!8K^Or*0GvGlY8QwA zAOHveQCuOVf3gkC6o@I%*&Yah3oK9+f!qbs=@0Tk9KRGVA^?gfJn~cFzJ7UmeeS7j zpTB+g`)cd66zGVpP^}25fY>1HC;+gGz>@!n&jcD|fBomaQgY6arKt_SRgTQM9vfe=eKuX4=%P%8({4mlR|JXf+79$Y`q>~KxWl{R_AN-c=Sot z@bn^ff&N^tuVY1UF4V_bgnROeIRo&ty4M0Br^hYync^Ynz>EO0O%yY5j}mj>@G42u zDZRJ%P)qree3tO&x$j!K-s?TQ`{kmp(|ckW)L zbeGs%jt3V2Jn7LArq+1M8cNG4rBs$04kN|eTG?C6y;g7cxc)!GY*6Qn@(-^70000< MMNUMnLSTXb0A>?38UO$Q literal 0 HcmV?d00001 diff --git a/public/assets/key.png b/public/assets/key.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d07a4db538736d040c047e77de9a2383b750b5 GIT binary patch literal 1086 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 { 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();