key lock / player curry collisions
This commit is contained in:
parent
4b9349b3f8
commit
cbb88091bd
BIN
public/assets/curry.png
Normal file
BIN
public/assets/curry.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -25,6 +25,10 @@ export class Game {
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
public addEntity(entity: Entity) {
|
||||
this.entities.set(entity.id, entity);
|
||||
}
|
||||
|
@ -1,12 +1,21 @@
|
||||
import { Game } from ".";
|
||||
import { Miscellaneous, loadAssets } from "./config";
|
||||
import { Player, FunctionBox, Wall, LambdaFactory } from "./entities";
|
||||
import {
|
||||
Player,
|
||||
FunctionBox,
|
||||
Wall,
|
||||
LambdaFactory,
|
||||
Key,
|
||||
LockedDoor,
|
||||
Curry,
|
||||
} from "./entities";
|
||||
import {
|
||||
Grid,
|
||||
FacingDirection,
|
||||
Input,
|
||||
Render,
|
||||
LambdaFactory as LambdaFactorySpawnSystem,
|
||||
Collision,
|
||||
} from "./systems";
|
||||
|
||||
export class TheAbstractionEngine {
|
||||
@ -39,6 +48,7 @@ export class TheAbstractionEngine {
|
||||
},
|
||||
),
|
||||
new LambdaFactorySpawnSystem(),
|
||||
new Collision(),
|
||||
new Render(this.ctx),
|
||||
].forEach((system) => this.game.addSystem(system));
|
||||
|
||||
@ -55,6 +65,15 @@ export class TheAbstractionEngine {
|
||||
|
||||
const factory = new LambdaFactory({ x: 6, y: 6 }, "λ x . (x)", 10);
|
||||
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() {
|
||||
|
@ -5,6 +5,9 @@ export enum Sprites {
|
||||
FUNCTION_BOX,
|
||||
WALL,
|
||||
LAMBDA_FACTORY,
|
||||
KEY,
|
||||
LOCKED_DOOR,
|
||||
CURRY,
|
||||
}
|
||||
|
||||
export interface SpriteSpec {
|
||||
@ -66,3 +69,30 @@ const lambdaFactorySpriteSpec = {
|
||||
sheet: "/assets/function_factory.png",
|
||||
};
|
||||
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);
|
||||
|
45
src/engine/entities/Curry.ts
Normal file
45
src/engine/entities/Curry.ts
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,4 +3,7 @@ export namespace EntityNames {
|
||||
export const FunctionBox = "FunctionBox";
|
||||
export const Wall = "Wall";
|
||||
export const LambdaFactory = "LambdaFactory";
|
||||
export const Key = "Key";
|
||||
export const LockedDoor = "LockedDoor";
|
||||
export const Curry = "Curry";
|
||||
}
|
||||
|
45
src/engine/entities/Key.ts
Normal file
45
src/engine/entities/Key.ts
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
45
src/engine/entities/LockedDoor.ts
Normal file
45
src/engine/entities/LockedDoor.ts
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,3 +4,6 @@ export * from "./Player";
|
||||
export * from "./FunctionBox";
|
||||
export * from "./Wall";
|
||||
export * from "./LambdaFactory";
|
||||
export * from "./Key";
|
||||
export * from "./LockedDoor";
|
||||
export * from "./Curry";
|
||||
|
103
src/engine/systems/Collision.ts
Normal file
103
src/engine/systems/Collision.ts
Normal 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();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { System, SystemNames } from ".";
|
||||
import { Collision, System, SystemNames } from ".";
|
||||
import { Game } from "..";
|
||||
import { Entity } from "../entities";
|
||||
import { PhysicsConstants } from "../config";
|
||||
@ -13,8 +13,8 @@ import { Coord2D, Direction, Dimension2D } from "../interfaces";
|
||||
import { clamp } from "../utils";
|
||||
|
||||
export class Grid extends System {
|
||||
private dimension: Dimension2D;
|
||||
private grid: Set<string>[][] = [];
|
||||
public dimension: Dimension2D;
|
||||
public grid: Set<string>[][] = [];
|
||||
|
||||
constructor(
|
||||
{ width: columns, height: rows }: Dimension2D,
|
||||
@ -108,10 +108,10 @@ export class Grid extends System {
|
||||
grid.movingDirection = Direction.NONE;
|
||||
entity.addComponent(grid); // default to not moving
|
||||
|
||||
let nextGridPosition = this.getNewGridPosition(
|
||||
let [currentPosition, nextGridPosition] = [
|
||||
gridPosition,
|
||||
movingDirection,
|
||||
);
|
||||
this.getNewGridPosition(gridPosition, movingDirection),
|
||||
];
|
||||
|
||||
const moving = new Set<string>();
|
||||
moving.add(entity.id);
|
||||
@ -122,11 +122,24 @@ export class Grid extends System {
|
||||
(id) => game.getEntity(id)!,
|
||||
);
|
||||
|
||||
if (
|
||||
entities.some((entity) =>
|
||||
entity.hasComponent(ComponentNames.Colliding),
|
||||
const collidingEntities = entities.filter((entity) =>
|
||||
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();
|
||||
break;
|
||||
}
|
||||
@ -148,6 +161,7 @@ export class Grid extends System {
|
||||
moving.add(pushableEntity.id);
|
||||
}
|
||||
|
||||
currentPosition = nextGridPosition;
|
||||
nextGridPosition = this.getNewGridPosition(
|
||||
nextGridPosition,
|
||||
movingDirection,
|
||||
@ -324,7 +338,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) || !game.getEntity(id)) {
|
||||
cell.delete(id);
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ export namespace SystemNames {
|
||||
export const FacingDirection = "FacingDirection";
|
||||
export const Grid = "Grid";
|
||||
export const LambdaFactory = "LambdaFactory";
|
||||
export const Collision = "Collision";
|
||||
}
|
||||
|
@ -5,3 +5,4 @@ export * from "./Input";
|
||||
export * from "./FacingDirection";
|
||||
export * from "./Grid";
|
||||
export * from "./LambdaFactory";
|
||||
export * from "./Collision";
|
||||
|
Loading…
Reference in New Issue
Block a user