level system!
This commit is contained in:
parent
808a44e854
commit
ebae24f5a3
BIN
public/assets/portal.png
Normal file
BIN
public/assets/portal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -17,7 +17,10 @@ export const Title = ({ setReady }: TitleProps) => {
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<p>WASD/arrow keys to move, space/enter to interact</p>
|
<p>
|
||||||
|
WASD/arrow keys to move, space/enter to interact after highlighting with
|
||||||
|
the mouse
|
||||||
|
</p>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -20,6 +20,11 @@ export class Game {
|
|||||||
this.componentEntities = new Map();
|
this.componentEntities = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resetState() {
|
||||||
|
this.entities.clear();
|
||||||
|
this.componentEntities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
this.lastTimeStamp = performance.now();
|
this.lastTimeStamp = performance.now();
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import { Game } from ".";
|
import { Game } from ".";
|
||||||
import { Miscellaneous, loadAssets } from "./config";
|
import { Miscellaneous, loadAssets } from "./config";
|
||||||
import {
|
import { LevelNames } from "./levels";
|
||||||
Player,
|
|
||||||
Wall,
|
|
||||||
LambdaFactory,
|
|
||||||
Key,
|
|
||||||
LockedDoor,
|
|
||||||
Curry,
|
|
||||||
FunctionApplication,
|
|
||||||
} from "./entities";
|
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
FacingDirection,
|
FacingDirection,
|
||||||
@ -18,6 +10,7 @@ import {
|
|||||||
GridSpawner,
|
GridSpawner,
|
||||||
Life,
|
Life,
|
||||||
Music,
|
Music,
|
||||||
|
Level,
|
||||||
} from "./systems";
|
} from "./systems";
|
||||||
|
|
||||||
export class TheAbstractionEngine {
|
export class TheAbstractionEngine {
|
||||||
@ -40,6 +33,7 @@ export class TheAbstractionEngine {
|
|||||||
const facingDirectionSystem = new FacingDirection(inputSystem);
|
const facingDirectionSystem = new FacingDirection(inputSystem);
|
||||||
|
|
||||||
[
|
[
|
||||||
|
new Level(LevelNames.LevelSelection),
|
||||||
inputSystem,
|
inputSystem,
|
||||||
facingDirectionSystem,
|
facingDirectionSystem,
|
||||||
new Grid(
|
new Grid(
|
||||||
@ -55,27 +49,6 @@ export class TheAbstractionEngine {
|
|||||||
new Music(),
|
new Music(),
|
||||||
new Render(this.ctx),
|
new Render(this.ctx),
|
||||||
].forEach((system) => this.game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
const player = new Player();
|
|
||||||
this.game.addEntity(player);
|
|
||||||
|
|
||||||
const wall = new Wall({ x: 5, y: 3 });
|
|
||||||
this.game.addEntity(wall);
|
|
||||||
|
|
||||||
const factory = new LambdaFactory({ x: 3, y: 3 }, "(λ (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);
|
|
||||||
|
|
||||||
const application = new FunctionApplication({ x: 5, y: 5 }, "(_INPUT key)");
|
|
||||||
this.game.addEntity(application);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public play() {
|
public play() {
|
||||||
|
@ -9,6 +9,8 @@ export enum Sprites {
|
|||||||
LOCKED_DOOR,
|
LOCKED_DOOR,
|
||||||
CURRY,
|
CURRY,
|
||||||
BUBBLE,
|
BUBBLE,
|
||||||
|
PORTAL,
|
||||||
|
GRASS,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpriteSpec {
|
export interface SpriteSpec {
|
||||||
@ -106,3 +108,21 @@ const bubbleSpriteSpec = {
|
|||||||
sheet: "/assets/bubble.png",
|
sheet: "/assets/bubble.png",
|
||||||
};
|
};
|
||||||
SPRITE_SPECS.set(Sprites.BUBBLE, bubbleSpriteSpec);
|
SPRITE_SPECS.set(Sprites.BUBBLE, bubbleSpriteSpec);
|
||||||
|
|
||||||
|
const portalSpriteSpec = {
|
||||||
|
msPerFrame: 200,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
frames: 3,
|
||||||
|
sheet: "/assets/portal.png",
|
||||||
|
};
|
||||||
|
SPRITE_SPECS.set(Sprites.PORTAL, portalSpriteSpec);
|
||||||
|
|
||||||
|
const grassSpriteSpec = {
|
||||||
|
msPerFrame: 200,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
frames: 3,
|
||||||
|
sheet: "/assets/grass.png",
|
||||||
|
};
|
||||||
|
SPRITE_SPECS.set(Sprites.GRASS, grassSpriteSpec);
|
||||||
|
@ -3,6 +3,8 @@ import { Game } from "..";
|
|||||||
import { BoundingBox, Colliding, Grid, Sprite } from "../components";
|
import { BoundingBox, Colliding, Grid, Sprite } from "../components";
|
||||||
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
||||||
import { Coord2D } from "../interfaces";
|
import { Coord2D } from "../interfaces";
|
||||||
|
import { LevelNames } from "../levels";
|
||||||
|
import { Level, SystemNames } from "../systems";
|
||||||
|
|
||||||
export class Curry extends Entity {
|
export class Curry extends Entity {
|
||||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||||
@ -45,9 +47,13 @@ export class Curry extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private collisionHandler(game: Game, entity: Entity) {
|
private collisionHandler(game: Game, entity: Entity) {
|
||||||
if (entity.name === EntityNames.Player) {
|
if (entity.name !== EntityNames.Player) {
|
||||||
game.removeEntity(this.id);
|
return;
|
||||||
game.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game.removeEntity(this.id);
|
||||||
|
|
||||||
|
const levelSystem = game.getSystem<Level>(SystemNames.Level);
|
||||||
|
levelSystem.setLevel(LevelNames.LevelSelection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,4 +8,6 @@ export namespace EntityNames {
|
|||||||
export const Curry = "Curry";
|
export const Curry = "Curry";
|
||||||
export const FunctionApplication = "FunctionApplication";
|
export const FunctionApplication = "FunctionApplication";
|
||||||
export const Particles = "Particles";
|
export const Particles = "Particles";
|
||||||
|
export const Portal = "Portal";
|
||||||
|
export const Grass = "Grass";
|
||||||
}
|
}
|
||||||
|
27
src/engine/entities/Grass.ts
Normal file
27
src/engine/entities/Grass.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Entity, EntityNames } from ".";
|
||||||
|
import { Grid, Sprite } from "../components";
|
||||||
|
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
||||||
|
import { Coord2D } from "../interfaces";
|
||||||
|
|
||||||
|
export class Grass extends Entity {
|
||||||
|
private static spriteSpec = SPRITE_SPECS.get(Sprites.GRASS) as SpriteSpec;
|
||||||
|
|
||||||
|
constructor(gridPosition: Coord2D) {
|
||||||
|
super(EntityNames.Grass);
|
||||||
|
|
||||||
|
this.addComponent(new Grid(gridPosition));
|
||||||
|
|
||||||
|
this.addComponent(
|
||||||
|
new Sprite(
|
||||||
|
IMAGES.get(Grass.spriteSpec.sheet)!,
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{
|
||||||
|
width: Grass.spriteSpec.width,
|
||||||
|
height: Grass.spriteSpec.height,
|
||||||
|
},
|
||||||
|
Grass.spriteSpec.msPerFrame,
|
||||||
|
Grass.spriteSpec.frames,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,14 +8,14 @@ import {
|
|||||||
Control,
|
Control,
|
||||||
Pushable,
|
Pushable,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Direction } from "../interfaces/";
|
import { Coord2D, Direction } from "../interfaces/";
|
||||||
|
|
||||||
export class Player extends Entity {
|
export class Player extends Entity {
|
||||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||||
Sprites.PLAYER,
|
Sprites.PLAYER,
|
||||||
) as SpriteSpec;
|
) as SpriteSpec;
|
||||||
|
|
||||||
constructor() {
|
constructor(gridPosition: Coord2D) {
|
||||||
super(EntityNames.Player);
|
super(EntityNames.Player);
|
||||||
|
|
||||||
this.addComponent(
|
this.addComponent(
|
||||||
@ -33,7 +33,7 @@ export class Player extends Entity {
|
|||||||
|
|
||||||
this.addComponent(new Control());
|
this.addComponent(new Control());
|
||||||
|
|
||||||
this.addComponent(new Grid());
|
this.addComponent(new Grid(gridPosition));
|
||||||
this.addFacingDirectionComponents();
|
this.addFacingDirectionComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
src/engine/entities/Portal.ts
Normal file
60
src/engine/entities/Portal.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Entity, EntityNames } from ".";
|
||||||
|
import { Game } from "..";
|
||||||
|
import { BoundingBox, Colliding, Grid, Sprite, Text } from "../components";
|
||||||
|
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
||||||
|
import { Coord2D } from "../interfaces";
|
||||||
|
import { Level, SystemNames } from "../systems";
|
||||||
|
|
||||||
|
export class Portal extends Entity {
|
||||||
|
private static spriteSpec = SPRITE_SPECS.get(Sprites.PORTAL) as SpriteSpec;
|
||||||
|
|
||||||
|
private toLevel: string;
|
||||||
|
|
||||||
|
constructor(toLevel: string, gridPosition: Coord2D) {
|
||||||
|
super(EntityNames.Portal);
|
||||||
|
|
||||||
|
this.toLevel = toLevel;
|
||||||
|
|
||||||
|
this.addComponent(
|
||||||
|
new BoundingBox(
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: Portal.spriteSpec.width,
|
||||||
|
height: Portal.spriteSpec.height,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addComponent(
|
||||||
|
new Sprite(
|
||||||
|
IMAGES.get(Portal.spriteSpec.sheet)!,
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
{
|
||||||
|
width: Portal.spriteSpec.width,
|
||||||
|
height: Portal.spriteSpec.height,
|
||||||
|
},
|
||||||
|
Portal.spriteSpec.msPerFrame,
|
||||||
|
Portal.spriteSpec.frames,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addComponent(new Colliding(this.handleCollision.bind(this)));
|
||||||
|
|
||||||
|
this.addComponent(new Grid(gridPosition));
|
||||||
|
|
||||||
|
this.addComponent(new Text(toLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleCollision(game: Game, entity: Entity) {
|
||||||
|
if (entity.name !== EntityNames.Player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelSystem = game.getSystem<Level>(SystemNames.Level);
|
||||||
|
levelSystem.setLevel(this.toLevel);
|
||||||
|
}
|
||||||
|
}
|
@ -9,3 +9,5 @@ export * from "./LockedDoor";
|
|||||||
export * from "./Curry";
|
export * from "./Curry";
|
||||||
export * from "./FunctionApplication";
|
export * from "./FunctionApplication";
|
||||||
export * from "./Particles";
|
export * from "./Particles";
|
||||||
|
export * from "./Portal";
|
||||||
|
export * from "./Grass";
|
||||||
|
11
src/engine/levels/Level.ts
Normal file
11
src/engine/levels/Level.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Game } from "..";
|
||||||
|
|
||||||
|
export abstract class Level {
|
||||||
|
public readonly name: string;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract init(game: Game): void;
|
||||||
|
}
|
4
src/engine/levels/LevelNames.ts
Normal file
4
src/engine/levels/LevelNames.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export namespace LevelNames {
|
||||||
|
export const Tutorial = "0";
|
||||||
|
export const LevelSelection = "LevelSelection";
|
||||||
|
}
|
33
src/engine/levels/LevelSelection.ts
Normal file
33
src/engine/levels/LevelSelection.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { LEVELS, Level, LevelNames } from ".";
|
||||||
|
import { Game } from "..";
|
||||||
|
import { Player, Portal } from "../entities";
|
||||||
|
import { Grid, Level as LevelSystem, SystemNames } from "../systems";
|
||||||
|
|
||||||
|
export class LevelSelection extends Level {
|
||||||
|
constructor() {
|
||||||
|
super(LevelNames.LevelSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(game: Game): void {
|
||||||
|
const gridSystem = game.getSystem<Grid>(SystemNames.Grid);
|
||||||
|
const center = gridSystem.getCenterGrid();
|
||||||
|
|
||||||
|
const levelSystem = game.getSystem<LevelSystem>(SystemNames.Level);
|
||||||
|
const unlocked = levelSystem.getUnlockedLevels();
|
||||||
|
|
||||||
|
LEVELS.forEach((level, i) => {
|
||||||
|
if (
|
||||||
|
!unlocked.has(level.name) ||
|
||||||
|
level.name === LevelNames.LevelSelection
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const portal = new Portal(level.name, { x: i, y: 7 });
|
||||||
|
game.addEntity(portal);
|
||||||
|
});
|
||||||
|
|
||||||
|
const player = new Player(center);
|
||||||
|
game.addEntity(player);
|
||||||
|
}
|
||||||
|
}
|
32
src/engine/levels/Tutorial.ts
Normal file
32
src/engine/levels/Tutorial.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Level, LevelNames } from ".";
|
||||||
|
import { Game } from "..";
|
||||||
|
import {
|
||||||
|
Curry,
|
||||||
|
FunctionApplication,
|
||||||
|
LambdaFactory,
|
||||||
|
LockedDoor,
|
||||||
|
Player,
|
||||||
|
Wall,
|
||||||
|
} from "../entities";
|
||||||
|
|
||||||
|
export class Tutorial extends Level {
|
||||||
|
constructor() {
|
||||||
|
super(LevelNames.Tutorial);
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(game: Game): void {
|
||||||
|
const entities = [
|
||||||
|
new Player({ x: 2, y: 2 }),
|
||||||
|
new Wall({ x: 10, y: 9 }),
|
||||||
|
new Wall({ x: 10, y: 11 }),
|
||||||
|
new Wall({ x: 11, y: 10 }),
|
||||||
|
new Curry({ x: 10, y: 10 }),
|
||||||
|
new LockedDoor({ x: 9, y: 10 }),
|
||||||
|
new LambdaFactory({ x: 6, y: 3 }, "(λ (x) . x)", 3),
|
||||||
|
|
||||||
|
new FunctionApplication({ x: 6, y: 6 }, "(_INPUT key)"),
|
||||||
|
];
|
||||||
|
|
||||||
|
entities.forEach((entity) => game.addEntity(entity));
|
||||||
|
}
|
||||||
|
}
|
12
src/engine/levels/index.ts
Normal file
12
src/engine/levels/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export * from "./LevelNames";
|
||||||
|
export * from "./Level";
|
||||||
|
export * from "./LevelSelection";
|
||||||
|
export * from "./Tutorial";
|
||||||
|
|
||||||
|
import { LevelNames } from ".";
|
||||||
|
import { LevelSelection, Tutorial, Level } from ".";
|
||||||
|
|
||||||
|
export const LEVELS: Level[] = [new LevelSelection(), new Tutorial()];
|
||||||
|
export const LEVEL_PROGRESSION = {
|
||||||
|
[LevelNames.LevelSelection]: [LevelNames.Tutorial],
|
||||||
|
};
|
6
src/engine/levels/utils.ts
Normal file
6
src/engine/levels/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Entity } from "../entities";
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//export const levelFormatToEntityList = (lines: string[]): Entity[] => {
|
||||||
|
//
|
||||||
|
//}
|
@ -7,6 +7,7 @@ const collisionMap: Record<string, Set<string>> = {
|
|||||||
[EntityNames.Key]: new Set([EntityNames.LockedDoor]),
|
[EntityNames.Key]: new Set([EntityNames.LockedDoor]),
|
||||||
[EntityNames.Curry]: new Set([EntityNames.Player]),
|
[EntityNames.Curry]: new Set([EntityNames.Player]),
|
||||||
[EntityNames.FunctionApplication]: new Set([EntityNames.FunctionBox]),
|
[EntityNames.FunctionApplication]: new Set([EntityNames.FunctionBox]),
|
||||||
|
[EntityNames.Portal]: new Set([EntityNames.Player]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Collision extends System {
|
export class Collision extends System {
|
||||||
|
@ -377,4 +377,11 @@ export class Grid extends System {
|
|||||||
this.grid[y][x].add(id);
|
this.grid[y][x].add(id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCenterGrid() {
|
||||||
|
return {
|
||||||
|
x: Math.floor(this.grid[0].length / 2),
|
||||||
|
y: Math.floor(this.grid.length / 2),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
57
src/engine/systems/Level.ts
Normal file
57
src/engine/systems/Level.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { SystemNames, System } from ".";
|
||||||
|
import { Game } from "..";
|
||||||
|
import { type Level as LevelType, LEVELS, LEVEL_PROGRESSION } from "../levels";
|
||||||
|
|
||||||
|
export class Level extends System {
|
||||||
|
private unlockedLevels: Set<string>;
|
||||||
|
private currentLevel: LevelType | null;
|
||||||
|
private moveToLevel: string | null;
|
||||||
|
private levelMap: Map<string, LevelType>;
|
||||||
|
|
||||||
|
constructor(initialLevel: string) {
|
||||||
|
super(SystemNames.Level);
|
||||||
|
|
||||||
|
this.levelMap = new Map();
|
||||||
|
LEVELS.forEach((level) => {
|
||||||
|
this.levelMap.set(level.name, level);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.currentLevel = null;
|
||||||
|
this.moveToLevel = initialLevel;
|
||||||
|
this.unlockedLevels = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLevel(level: string) {
|
||||||
|
this.moveToLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(_dt: number, game: Game) {
|
||||||
|
if (this.moveToLevel === this.currentLevel?.name || !this.moveToLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentLevel) {
|
||||||
|
game.resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlockedLevels = LEVEL_PROGRESSION[this.moveToLevel];
|
||||||
|
if (unlockedLevels && unlockedLevels.length > 0) {
|
||||||
|
unlockedLevels.forEach((levelName) => {
|
||||||
|
if (!this.unlockedLevels.has(levelName)) {
|
||||||
|
this.unlockedLevels.add(levelName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.unlockedLevels.has(this.moveToLevel)) {
|
||||||
|
this.unlockedLevels.add(this.moveToLevel);
|
||||||
|
}
|
||||||
|
this.currentLevel = this.levelMap.get(this.moveToLevel)!;
|
||||||
|
this.currentLevel.init(game);
|
||||||
|
this.moveToLevel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUnlockedLevels() {
|
||||||
|
return this.unlockedLevels;
|
||||||
|
}
|
||||||
|
}
|
@ -7,4 +7,5 @@ export namespace SystemNames {
|
|||||||
export const Collision = "Collision";
|
export const Collision = "Collision";
|
||||||
export const Life = "Life";
|
export const Life = "Life";
|
||||||
export const Music = "Music";
|
export const Music = "Music";
|
||||||
|
export const Level = "Level";
|
||||||
}
|
}
|
||||||
|
@ -8,3 +8,4 @@ export * from "./GridSpawner";
|
|||||||
export * from "./Collision";
|
export * from "./Collision";
|
||||||
export * from "./Life";
|
export * from "./Life";
|
||||||
export * from "./Music";
|
export * from "./Music";
|
||||||
|
export * from "./Level";
|
||||||
|
Loading…
Reference in New Issue
Block a user