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>
|
||||
</p>
|
||||
<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 />
|
||||
<hr />
|
||||
|
@ -20,6 +20,11 @@ export class Game {
|
||||
this.componentEntities = new Map();
|
||||
}
|
||||
|
||||
public resetState() {
|
||||
this.entities.clear();
|
||||
this.componentEntities.clear();
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.lastTimeStamp = performance.now();
|
||||
this.running = true;
|
||||
|
@ -1,14 +1,6 @@
|
||||
import { Game } from ".";
|
||||
import { Miscellaneous, loadAssets } from "./config";
|
||||
import {
|
||||
Player,
|
||||
Wall,
|
||||
LambdaFactory,
|
||||
Key,
|
||||
LockedDoor,
|
||||
Curry,
|
||||
FunctionApplication,
|
||||
} from "./entities";
|
||||
import { LevelNames } from "./levels";
|
||||
import {
|
||||
Grid,
|
||||
FacingDirection,
|
||||
@ -18,6 +10,7 @@ import {
|
||||
GridSpawner,
|
||||
Life,
|
||||
Music,
|
||||
Level,
|
||||
} from "./systems";
|
||||
|
||||
export class TheAbstractionEngine {
|
||||
@ -40,6 +33,7 @@ export class TheAbstractionEngine {
|
||||
const facingDirectionSystem = new FacingDirection(inputSystem);
|
||||
|
||||
[
|
||||
new Level(LevelNames.LevelSelection),
|
||||
inputSystem,
|
||||
facingDirectionSystem,
|
||||
new Grid(
|
||||
@ -55,27 +49,6 @@ export class TheAbstractionEngine {
|
||||
new Music(),
|
||||
new Render(this.ctx),
|
||||
].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() {
|
||||
|
@ -9,6 +9,8 @@ export enum Sprites {
|
||||
LOCKED_DOOR,
|
||||
CURRY,
|
||||
BUBBLE,
|
||||
PORTAL,
|
||||
GRASS,
|
||||
}
|
||||
|
||||
export interface SpriteSpec {
|
||||
@ -106,3 +108,21 @@ const bubbleSpriteSpec = {
|
||||
sheet: "/assets/bubble.png",
|
||||
};
|
||||
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 { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
||||
import { Coord2D } from "../interfaces";
|
||||
import { LevelNames } from "../levels";
|
||||
import { Level, SystemNames } from "../systems";
|
||||
|
||||
export class Curry extends Entity {
|
||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||
@ -45,9 +47,13 @@ export class Curry extends Entity {
|
||||
}
|
||||
|
||||
private collisionHandler(game: Game, entity: Entity) {
|
||||
if (entity.name === EntityNames.Player) {
|
||||
game.removeEntity(this.id);
|
||||
game.stop();
|
||||
if (entity.name !== EntityNames.Player) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 FunctionApplication = "FunctionApplication";
|
||||
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,
|
||||
Pushable,
|
||||
} from "../components";
|
||||
import { Direction } from "../interfaces/";
|
||||
import { Coord2D, Direction } from "../interfaces/";
|
||||
|
||||
export class Player extends Entity {
|
||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||
Sprites.PLAYER,
|
||||
) as SpriteSpec;
|
||||
|
||||
constructor() {
|
||||
constructor(gridPosition: Coord2D) {
|
||||
super(EntityNames.Player);
|
||||
|
||||
this.addComponent(
|
||||
@ -33,7 +33,7 @@ export class Player extends Entity {
|
||||
|
||||
this.addComponent(new Control());
|
||||
|
||||
this.addComponent(new Grid());
|
||||
this.addComponent(new Grid(gridPosition));
|
||||
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 "./FunctionApplication";
|
||||
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.Curry]: new Set([EntityNames.Player]),
|
||||
[EntityNames.FunctionApplication]: new Set([EntityNames.FunctionBox]),
|
||||
[EntityNames.Portal]: new Set([EntityNames.Player]),
|
||||
};
|
||||
|
||||
export class Collision extends System {
|
||||
|
@ -377,4 +377,11 @@ export class Grid extends System {
|
||||
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 Life = "Life";
|
||||
export const Music = "Music";
|
||||
export const Level = "Level";
|
||||
}
|
||||
|
@ -8,3 +8,4 @@ export * from "./GridSpawner";
|
||||
export * from "./Collision";
|
||||
export * from "./Life";
|
||||
export * from "./Music";
|
||||
export * from "./Level";
|
||||
|
Loading…
Reference in New Issue
Block a user