slight refactor in collision behavior

This commit is contained in:
Elizabeth Hunt 2024-03-02 02:22:46 -07:00
parent cd6a3a56b0
commit 06bb417720
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
16 changed files with 209 additions and 62 deletions

View File

@ -1,32 +1,30 @@
/* The Modal (background) */
.modal { .modal {
display: none; /* Hidden by default */ display: none;
position: fixed; /* Stay in place */ position: fixed;
z-index: 1; /* Sit on top */ z-index: 1;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; /* Full width */ width: 100%;
height: 100%; /* Full height */ height: 100%;
justify-content: center; /* Center horizontally */ justify-content: center;
align-items: center; /* Center vertically */ align-items: center;
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ background-color: rgba(0, 0, 0, 0.4);
overflow: auto; /* Enable scroll if needed */ overflow: auto;
animation: fadeIn 0.25s; /* Fade in the background */ animation: fadeIn 0.25s;
} }
/* Modal Content */
.modal-content { .modal-content {
display: flex; display: flex;
background-color: #282828; /* Gruvbox background */ background-color: #282828;
color: #ebdbb2; /* Gruvbox foreground */ color: #ebdbb2;
margin: auto; margin: auto;
padding: 20px; padding: 20px;
border: 1px solid #928374; /* Gruvbox grey */ border: 1px solid #928374;
width: 40%; /* Adjust as needed */ width: 40%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
animation: scaleUp 0.25s; /* Scale animation */ animation: scaleUp 0.25s;
border-radius: 8px; /* Rounded corners */ border-radius: 8px;
justify-content: center; /* Center horizontally */ justify-content: center;
} }
/* Animations */ /* Animations */

View File

@ -1,8 +1,7 @@
import { Game } from "."; import { Game } from ".";
import { Miscellaneous, loadAssets } from "./config"; import { Miscellaneous, loadAssets } from "./config";
import { Player, FunctionBox } from "./entities"; import { Player, FunctionBox, Wall } from "./entities";
import { FacingDirection, Input, Render } from "./systems"; import { Grid, FacingDirection, Input, Render } from "./systems";
import { Grid } from "./systems/Grid";
export class TheAbstractionEngine { export class TheAbstractionEngine {
private game: Game; private game: Game;
@ -24,7 +23,6 @@ export class TheAbstractionEngine {
const facingDirectionSystem = new FacingDirection(inputSystem); const facingDirectionSystem = new FacingDirection(inputSystem);
[ [
new Render(this.ctx),
inputSystem, inputSystem,
facingDirectionSystem, facingDirectionSystem,
new Grid( new Grid(
@ -34,6 +32,7 @@ export class TheAbstractionEngine {
height: Miscellaneous.GRID_CELL_HEIGHT, height: Miscellaneous.GRID_CELL_HEIGHT,
}, },
), ),
new Render(this.ctx),
].forEach((system) => this.game.addSystem(system)); ].forEach((system) => this.game.addSystem(system));
const player = new Player(); const player = new Player();
@ -43,6 +42,9 @@ export class TheAbstractionEngine {
this.game.addEntity(box); this.game.addEntity(box);
const box2 = new FunctionBox({ x: 4, y: 1 }, "λ x . (x)"); const box2 = new FunctionBox({ x: 4, y: 1 }, "λ x . (x)");
this.game.addEntity(box2); this.game.addEntity(box2);
const wall = new Wall({ x: 5, y: 3 });
this.game.addEntity(wall);
} }
public play() { public play() {

View File

@ -0,0 +1,7 @@
import { Component, ComponentNames } from ".";
export class Colliding extends Component {
constructor() {
super(ComponentNames.Colliding);
}
}

View File

@ -6,4 +6,6 @@ export namespace ComponentNames {
export const Control = "Control"; export const Control = "Control";
export const Highlight = "Highlight"; export const Highlight = "Highlight";
export const Interactable = "Interactable"; export const Interactable = "Interactable";
export const Pushable = "Pushable";
export const Colliding = "Colliding";
} }

View File

@ -6,15 +6,12 @@ export class Grid extends Component {
public gridPosition: Coord2D; public gridPosition: Coord2D;
public movingDirection: Direction; public movingDirection: Direction;
public pushable: boolean = false;
constructor(pushable: boolean = false, position: Coord2D = { x: 0, y: 0 }) { constructor(position: Coord2D = { x: 0, y: 0 }) {
super(ComponentNames.Grid); super(ComponentNames.Grid);
this.initialized = false; this.initialized = false;
this.gridPosition = position; this.gridPosition = position;
this.movingDirection = Direction.NONE; this.movingDirection = Direction.NONE;
this.pushable = pushable;
} }
} }

View File

@ -1,7 +1,33 @@
import { Component, ComponentNames } from "."; import { Component, ComponentNames } from ".";
export class Highlight extends Component { export class Highlight extends Component {
constructor() { public isHighlighted: boolean;
private onHighlight: Function;
private onUnhighlight: Function;
constructor(
onHighlight: Function,
onUnhighlight: Function,
isHighlighted: boolean = false,
) {
super(ComponentNames.Highlight); super(ComponentNames.Highlight);
this.isHighlighted = isHighlighted;
this.onHighlight = onHighlight;
this.onUnhighlight = onUnhighlight;
}
public highlight() {
if (!this.isHighlighted) {
this.isHighlighted = true;
this.onHighlight();
}
}
public unhighlight() {
if (this.isHighlighted) {
this.isHighlighted = false;
this.onUnhighlight();
}
} }
} }

View File

@ -0,0 +1,11 @@
import { Component, ComponentNames } from ".";
export class Pushable extends Component {
public pushable: boolean;
constructor(pushable = false) {
super(ComponentNames.Pushable);
this.pushable = pushable;
}
}

View File

@ -7,3 +7,5 @@ export * from "./BoundingBox";
export * from "./Control"; export * from "./Control";
export * from "./Highlight"; export * from "./Highlight";
export * from "./Interactable"; export * from "./Interactable";
export * from "./Pushable";
export * from "./Colliding";

View File

@ -3,6 +3,7 @@ import { Direction } from "../interfaces/Direction";
export enum Sprites { export enum Sprites {
PLAYER, PLAYER,
FUNCTION_BOX, FUNCTION_BOX,
WALL,
} }
export interface SpriteSpec { export interface SpriteSpec {
@ -46,3 +47,12 @@ const functionBoxSpriteSpec = {
sheet: "/assets/function_block.png", sheet: "/assets/function_block.png",
}; };
SPRITE_SPECS.set(Sprites.FUNCTION_BOX, functionBoxSpriteSpec); SPRITE_SPECS.set(Sprites.FUNCTION_BOX, functionBoxSpriteSpec);
const wallSpriteSpec = {
msPerFrame: 200,
width: 64,
height: 64,
frames: 3,
sheet: "/assets/wall.png",
};
SPRITE_SPECS.set(Sprites.WALL, wallSpriteSpec);

View File

@ -1,4 +1,5 @@
export namespace EntityNames { export namespace EntityNames {
export const Player = "Player"; export const Player = "Player";
export const FunctionBox = "FunctionBox"; export const FunctionBox = "FunctionBox";
export const Wall = "Wall";
} }

View File

@ -4,7 +4,9 @@ import {
BoundingBox, BoundingBox,
ComponentNames, ComponentNames,
Grid, Grid,
Highlight,
Interactable, Interactable,
Pushable,
Sprite, Sprite,
} from "../components"; } from "../components";
import { Coord2D } from "../interfaces"; import { Coord2D } from "../interfaces";
@ -36,7 +38,9 @@ export class FunctionBox extends Entity {
), ),
); );
this.addComponent(new Grid(true, gridPosition)); this.addComponent(new Pushable());
this.addComponent(new Grid(gridPosition));
this.addComponent( this.addComponent(
new Sprite( new Sprite(
@ -51,8 +55,20 @@ export class FunctionBox extends Entity {
), ),
); );
this.hooks.set(ComponentNames.Highlight, { this.addComponent(
add: () => { new Highlight(
() => this.onHighlight(),
() => this.onUnhighlight(),
),
);
}
private onUnhighlight() {
closeModal();
this.removeComponent(ComponentNames.Interactable);
}
private onHighlight() {
let modalOpen = false; let modalOpen = false;
const interaction = () => { const interaction = () => {
if (modalOpen) { if (modalOpen) {
@ -65,11 +81,5 @@ export class FunctionBox extends Entity {
}; };
this.addComponent(new Interactable(interaction)); this.addComponent(new Interactable(interaction));
},
remove: () => {
closeModal();
this.removeComponent(ComponentNames.Interactable);
},
});
} }
} }

View File

@ -0,0 +1,45 @@
import { Entity, EntityNames } from ".";
import { BoundingBox, Colliding, Grid, Sprite } from "../components";
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
import { Coord2D } from "../interfaces";
export class Wall extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.WALL,
) as SpriteSpec;
constructor(gridPosition: Coord2D) {
super(EntityNames.Wall);
this.addComponent(new Grid(gridPosition));
this.addComponent(new Colliding());
this.addComponent(
new BoundingBox(
{
x: 0,
y: 0,
},
{
width: Wall.spriteSpec.width,
height: Wall.spriteSpec.height,
},
0,
),
);
this.addComponent(
new Sprite(
IMAGES.get(Wall.spriteSpec.sheet)!,
{ x: 0, y: 0 },
{
width: Wall.spriteSpec.width,
height: Wall.spriteSpec.height,
},
Wall.spriteSpec.msPerFrame,
Wall.spriteSpec.frames,
),
);
}
}

View File

@ -2,3 +2,4 @@ export * from "./Entity";
export * from "./EntityNames"; export * from "./EntityNames";
export * from "./Player"; export * from "./Player";
export * from "./FunctionBox"; export * from "./FunctionBox";
export * from "./Wall";

View File

@ -71,14 +71,20 @@ export class Grid extends System {
highlightableEntities.forEach((id) => { highlightableEntities.forEach((id) => {
const entity = game.getEntity(id)!; const entity = game.getEntity(id)!;
if (!entity.hasComponent(ComponentNames.Highlight)) { if (entity.hasComponent(ComponentNames.Highlight)) {
entity.addComponent(new Highlight()); const highlight = entity.getComponent<Highlight>(
ComponentNames.Highlight,
)!;
highlight.highlight();
} }
}); });
game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => { game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => {
if (!highlightableEntities.has(entity.id)) { if (!highlightableEntities.has(entity.id)) {
entity.removeComponent(ComponentNames.Highlight); const highlight = entity.getComponent<Highlight>(
ComponentNames.Highlight,
)!;
highlight.unhighlight();
} }
}); });
} }
@ -97,21 +103,41 @@ export class Grid extends System {
// continue until no more pushable entities are found // continue until no more pushable entities are found
for (const entity of movingEntities) { for (const entity of movingEntities) {
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!; const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
const { gridPosition, movingDirection } = grid;
grid.movingDirection = Direction.NONE;
entity.addComponent(grid); // default to not moving
let nextGridPosition = this.getNewGridPosition( let nextGridPosition = this.getNewGridPosition(
grid.gridPosition, gridPosition,
grid.movingDirection, movingDirection,
); );
const moving = new Set<string>();
moving.add(entity.id);
while (!this.isOutOfBounds(nextGridPosition)) { while (!this.isOutOfBounds(nextGridPosition)) {
const { x, y } = nextGridPosition; const { x, y } = nextGridPosition;
const entities = Array.from(this.grid[y][x]).map( const entities = Array.from(this.grid[y][x]).map(
(id) => game.getEntity(id)!, (id) => game.getEntity(id)!,
); );
if (
entities.some((entity) =>
entity.hasComponent(ComponentNames.Colliding),
)
) {
moving.clear();
break;
}
const pushableEntities = entities.filter((entity) => { const pushableEntities = entities.filter((entity) => {
if (!entity.hasComponent(ComponentNames.Grid)) return false; if (!entity.hasComponent(ComponentNames.Grid)) return false;
const { pushable, movingDirection } = const { movingDirection } = entity.getComponent<GridComponent>(
entity.getComponent<GridComponent>(ComponentNames.Grid)!; ComponentNames.Grid,
)!;
const pushable = entity.hasComponent(ComponentNames.Pushable);
return movingDirection === Direction.NONE && pushable; return movingDirection === Direction.NONE && pushable;
}); });
if (pushableEntities.length === 0) { if (pushableEntities.length === 0) {
@ -119,18 +145,21 @@ export class Grid extends System {
} }
for (const pushableEntity of pushableEntities) { for (const pushableEntity of pushableEntities) {
const pushableGrid = pushableEntity.getComponent<GridComponent>( moving.add(pushableEntity.id);
ComponentNames.Grid,
)!;
pushableGrid.movingDirection = grid.movingDirection;
pushableEntity.addComponent(pushableEntity);
} }
nextGridPosition = this.getNewGridPosition( nextGridPosition = this.getNewGridPosition(
nextGridPosition, nextGridPosition,
grid.movingDirection, movingDirection,
); );
} }
for (const id of moving) {
const entity = game.getEntity(id)!;
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
grid.movingDirection = movingDirection;
entity.addComponent(grid);
}
} }
} }

View File

@ -1,5 +1,5 @@
import { System, SystemNames } from "."; import { System, SystemNames } from ".";
import { BoundingBox, ComponentNames, Sprite } from "../components"; import { BoundingBox, ComponentNames, Highlight, Sprite } from "../components";
import { Game } from ".."; import { Game } from "..";
import { clamp } from "../utils"; import { clamp } from "../utils";
import { DrawArgs } from "../interfaces"; import { DrawArgs } from "../interfaces";
@ -43,8 +43,13 @@ export class Render extends System {
center: boundingBox.center, center: boundingBox.center,
dimension: boundingBox.dimension, dimension: boundingBox.dimension,
rotation: boundingBox.rotation, rotation: boundingBox.rotation,
tint: entity.hasComponent(ComponentNames.Highlight) ? "red" : undefined,
}; };
if (entity.hasComponent(ComponentNames.Highlight)) {
const highlight = entity.getComponent<Highlight>(
ComponentNames.Highlight,
);
drawArgs.tint = highlight.isHighlighted ? "red" : undefined;
}
sprite.draw(this.ctx, drawArgs); sprite.draw(this.ctx, drawArgs);
}); });

View File

@ -3,3 +3,4 @@ export * from "./System";
export * from "./Render"; export * from "./Render";
export * from "./Input"; export * from "./Input";
export * from "./FacingDirection"; export * from "./FacingDirection";
export * from "./Grid";