key lock / player curry collisions

This commit is contained in:
Elizabeth Hunt 2024-03-02 05:30:17 -07:00
parent 4b9349b3f8
commit cbb88091bd
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
13 changed files with 325 additions and 12 deletions

BIN
public/assets/curry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -25,6 +25,10 @@ export class Game {
this.running = true; this.running = true;
} }
public stop() {
this.running = false;
}
public addEntity(entity: Entity) { public addEntity(entity: Entity) {
this.entities.set(entity.id, entity); this.entities.set(entity.id, entity);
} }

View File

@ -1,12 +1,21 @@
import { Game } from "."; import { Game } from ".";
import { Miscellaneous, loadAssets } from "./config"; import { Miscellaneous, loadAssets } from "./config";
import { Player, FunctionBox, Wall, LambdaFactory } from "./entities"; import {
Player,
FunctionBox,
Wall,
LambdaFactory,
Key,
LockedDoor,
Curry,
} from "./entities";
import { import {
Grid, Grid,
FacingDirection, FacingDirection,
Input, Input,
Render, Render,
LambdaFactory as LambdaFactorySpawnSystem, LambdaFactory as LambdaFactorySpawnSystem,
Collision,
} from "./systems"; } from "./systems";
export class TheAbstractionEngine { export class TheAbstractionEngine {
@ -39,6 +48,7 @@ export class TheAbstractionEngine {
}, },
), ),
new LambdaFactorySpawnSystem(), new LambdaFactorySpawnSystem(),
new Collision(),
new Render(this.ctx), new Render(this.ctx),
].forEach((system) => this.game.addSystem(system)); ].forEach((system) => this.game.addSystem(system));
@ -55,6 +65,15 @@ export class TheAbstractionEngine {
const factory = new LambdaFactory({ x: 6, y: 6 }, "λ x . (x)", 10); const factory = new LambdaFactory({ x: 6, y: 6 }, "λ x . (x)", 10);
this.game.addEntity(factory); this.game.addEntity(factory);
const lockedDoor = new LockedDoor({ x: 8, y: 8 });
this.game.addEntity(lockedDoor);
const key = new Key({ x: 7, y: 7 });
this.game.addEntity(key);
const curry = new Curry({ x: 9, y: 8 });
this.game.addEntity(curry);
} }
public play() { public play() {

View File

@ -5,6 +5,9 @@ export enum Sprites {
FUNCTION_BOX, FUNCTION_BOX,
WALL, WALL,
LAMBDA_FACTORY, LAMBDA_FACTORY,
KEY,
LOCKED_DOOR,
CURRY,
} }
export interface SpriteSpec { export interface SpriteSpec {
@ -66,3 +69,30 @@ const lambdaFactorySpriteSpec = {
sheet: "/assets/function_factory.png", sheet: "/assets/function_factory.png",
}; };
SPRITE_SPECS.set(Sprites.LAMBDA_FACTORY, lambdaFactorySpriteSpec); SPRITE_SPECS.set(Sprites.LAMBDA_FACTORY, lambdaFactorySpriteSpec);
const keySpriteSpec = {
msPerFrame: 200,
width: 64,
height: 64,
frames: 3,
sheet: "/assets/key.png",
};
SPRITE_SPECS.set(Sprites.KEY, keySpriteSpec);
const lockedDoorSpriteSpec = {
msPerFrame: 200,
width: 64,
height: 64,
frames: 3,
sheet: "/assets/locked_door.png",
};
SPRITE_SPECS.set(Sprites.LOCKED_DOOR, lockedDoorSpriteSpec);
const currySpriteSpec = {
msPerFrame: 200,
width: 64,
height: 64,
frames: 3,
sheet: "/assets/curry.png",
};
SPRITE_SPECS.set(Sprites.CURRY, currySpriteSpec);

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 Curry extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.CURRY,
) as SpriteSpec;
constructor(gridPosition: Coord2D) {
super(EntityNames.Curry);
this.addComponent(new Grid(gridPosition));
this.addComponent(new Colliding());
this.addComponent(
new BoundingBox(
{
x: 0,
y: 0,
},
{
width: Curry.spriteSpec.width,
height: Curry.spriteSpec.height,
},
0,
),
);
this.addComponent(
new Sprite(
IMAGES.get(Curry.spriteSpec.sheet)!,
{ x: 0, y: 0 },
{
width: Curry.spriteSpec.width,
height: Curry.spriteSpec.height,
},
Curry.spriteSpec.msPerFrame,
Curry.spriteSpec.frames,
),
);
}
}

View File

@ -3,4 +3,7 @@ export namespace EntityNames {
export const FunctionBox = "FunctionBox"; export const FunctionBox = "FunctionBox";
export const Wall = "Wall"; export const Wall = "Wall";
export const LambdaFactory = "LambdaFactory"; export const LambdaFactory = "LambdaFactory";
export const Key = "Key";
export const LockedDoor = "LockedDoor";
export const Curry = "Curry";
} }

View File

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

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 LockedDoor extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.LOCKED_DOOR,
) as SpriteSpec;
constructor(gridPosition: Coord2D) {
super(EntityNames.LockedDoor);
this.addComponent(new Grid(gridPosition));
this.addComponent(new Colliding());
this.addComponent(
new BoundingBox(
{
x: 0,
y: 0,
},
{
width: LockedDoor.spriteSpec.width,
height: LockedDoor.spriteSpec.height,
},
0,
),
);
this.addComponent(
new Sprite(
IMAGES.get(LockedDoor.spriteSpec.sheet)!,
{ x: 0, y: 0 },
{
width: LockedDoor.spriteSpec.width,
height: LockedDoor.spriteSpec.height,
},
LockedDoor.spriteSpec.msPerFrame,
LockedDoor.spriteSpec.frames,
),
);
}
}

View File

@ -4,3 +4,6 @@ export * from "./Player";
export * from "./FunctionBox"; export * from "./FunctionBox";
export * from "./Wall"; export * from "./Wall";
export * from "./LambdaFactory"; export * from "./LambdaFactory";
export * from "./Key";
export * from "./LockedDoor";
export * from "./Curry";

View File

@ -0,0 +1,103 @@
import { System, SystemNames } from ".";
import { Game } from "..";
import { Entity, EntityNames } from "../entities";
import { BoundingBox, Colliding, ComponentNames, Grid } from "../components";
const collisionMap: Record<string, Set<string>> = {
[EntityNames.Key]: new Set([EntityNames.LockedDoor]),
[EntityNames.Curry]: new Set([EntityNames.Player]),
};
export class Collision extends System {
static canCollide(entityName: string, otherEntityName: string) {
if (collisionMap[entityName]) {
return collisionMap[entityName].has(otherEntityName);
}
return collisionMap[otherEntityName]?.has(entityName) ?? false;
}
constructor() {
super(SystemNames.Collision);
}
public update(_dt: number, game: Game) {
game.forEachEntityWithComponent(ComponentNames.Colliding, (entity) => {
if (!entity.hasComponent(ComponentNames.BoundingBox)) {
return;
}
const collidingBox = entity.getComponent<BoundingBox>(
ComponentNames.BoundingBox,
);
let collidingGrid = entity.hasComponent(ComponentNames.Grid)
? entity.getComponent<Grid>(ComponentNames.Grid)
: null;
const collidingWith: Entity[] = [];
game.forEachEntityWithComponent(
ComponentNames.BoundingBox,
(otherEntity) => {
const otherBoundingBox = otherEntity.getComponent<BoundingBox>(
ComponentNames.BoundingBox,
);
let otherGrid = otherEntity.hasComponent(ComponentNames.Grid)
? otherEntity.getComponent<Grid>(ComponentNames.Grid)
: null;
if (collidingGrid && otherGrid) {
if (
collidingGrid.gridPosition.x === otherGrid.gridPosition.x &&
collidingGrid.gridPosition.y === otherGrid.gridPosition.y
) {
collidingWith.push(otherEntity);
}
return;
}
if (collidingBox.isCollidingWith(otherBoundingBox)) {
collidingWith.push(otherEntity);
}
},
);
for (const collision of collidingWith) {
this.handleCollision(entity, collision, game);
}
});
}
private handleCollision(entity: Entity, otherEntity: Entity, game: Game) {
if (!Collision.canCollide(entity.name, otherEntity.name)) {
return;
}
const keyDoorPair = [EntityNames.Key, EntityNames.LockedDoor].map((x) =>
[entity, otherEntity].find((y) => y.name === x),
);
const [key, door] = keyDoorPair;
if (key && door) {
this.handleKeyDoorCollision(key, door, game);
}
const curryPlayerPair = [EntityNames.Curry, EntityNames.Player].map((x) =>
[entity, otherEntity].find((y) => y.name === x),
);
const [curry, player] = curryPlayerPair;
if (curry && player) {
this.handleCurryPlayerCollision(curry, player, game);
}
}
private handleKeyDoorCollision(key: Entity, door: Entity, game: Game) {
game.removeEntity(key.id);
game.removeEntity(door.id);
}
private handleCurryPlayerCollision(
curry: Entity,
_player: Entity,
game: Game,
) {
game.removeEntity(curry.id);
game.stop();
}
}

View File

@ -1,4 +1,4 @@
import { System, SystemNames } from "."; import { Collision, System, SystemNames } from ".";
import { Game } from ".."; import { Game } from "..";
import { Entity } from "../entities"; import { Entity } from "../entities";
import { PhysicsConstants } from "../config"; import { PhysicsConstants } from "../config";
@ -13,8 +13,8 @@ import { Coord2D, Direction, Dimension2D } from "../interfaces";
import { clamp } from "../utils"; import { clamp } from "../utils";
export class Grid extends System { export class Grid extends System {
private dimension: Dimension2D; public dimension: Dimension2D;
private grid: Set<string>[][] = []; public grid: Set<string>[][] = [];
constructor( constructor(
{ width: columns, height: rows }: Dimension2D, { width: columns, height: rows }: Dimension2D,
@ -108,10 +108,10 @@ export class Grid extends System {
grid.movingDirection = Direction.NONE; grid.movingDirection = Direction.NONE;
entity.addComponent(grid); // default to not moving entity.addComponent(grid); // default to not moving
let nextGridPosition = this.getNewGridPosition( let [currentPosition, nextGridPosition] = [
gridPosition, gridPosition,
movingDirection, this.getNewGridPosition(gridPosition, movingDirection),
); ];
const moving = new Set<string>(); const moving = new Set<string>();
moving.add(entity.id); moving.add(entity.id);
@ -122,11 +122,24 @@ export class Grid extends System {
(id) => game.getEntity(id)!, (id) => game.getEntity(id)!,
); );
if ( const collidingEntities = entities.filter((entity) =>
entities.some((entity) =>
entity.hasComponent(ComponentNames.Colliding), entity.hasComponent(ComponentNames.Colliding),
);
if (collidingEntities.length > 0) {
// i.e. key going into a door or function going into an application
const allEntitiesInPreviousCellCanCollide = Array.from(
this.grid[currentPosition.y][currentPosition.x],
) )
) { .map((id) => game.getEntity(id)!)
.every((entity) =>
collidingEntities.every((collidingEntity) =>
Collision.canCollide(entity.name, collidingEntity.name),
),
);
if (allEntitiesInPreviousCellCanCollide) {
break;
}
moving.clear(); moving.clear();
break; break;
} }
@ -148,6 +161,7 @@ export class Grid extends System {
moving.add(pushableEntity.id); moving.add(pushableEntity.id);
} }
currentPosition = nextGridPosition;
nextGridPosition = this.getNewGridPosition( nextGridPosition = this.getNewGridPosition(
nextGridPosition, nextGridPosition,
movingDirection, movingDirection,
@ -324,7 +338,7 @@ export class Grid extends System {
this.grid.forEach((row) => this.grid.forEach((row) =>
row.forEach((cell) => { row.forEach((cell) => {
for (const id of cell) { for (const id of cell) {
if (movedEntities.has(id)) { if (movedEntities.has(id) || !game.getEntity(id)) {
cell.delete(id); cell.delete(id);
} }
} }

View File

@ -4,4 +4,5 @@ export namespace SystemNames {
export const FacingDirection = "FacingDirection"; export const FacingDirection = "FacingDirection";
export const Grid = "Grid"; export const Grid = "Grid";
export const LambdaFactory = "LambdaFactory"; export const LambdaFactory = "LambdaFactory";
export const Collision = "Collision";
} }

View File

@ -5,3 +5,4 @@ export * from "./Input";
export * from "./FacingDirection"; export * from "./FacingDirection";
export * from "./Grid"; export * from "./Grid";
export * from "./LambdaFactory"; export * from "./LambdaFactory";
export * from "./Collision";