the great engine refactor
This commit is contained in:
parent
b67ffb57c1
commit
c6e9baa000
@ -17,11 +17,6 @@ export class JumpStorm {
|
|||||||
this.game = new Game();
|
this.game = new Game();
|
||||||
this.socket = new WebSocket("ws://localhost:8080");
|
this.socket = new WebSocket("ws://localhost:8080");
|
||||||
|
|
||||||
this.socket.onopen = () => {
|
|
||||||
this.socket.send("gaming");
|
|
||||||
console.log("OPENED SOCKET");
|
|
||||||
};
|
|
||||||
|
|
||||||
[
|
[
|
||||||
this.createInputSystem(),
|
this.createInputSystem(),
|
||||||
new FacingDirection(),
|
new FacingDirection(),
|
||||||
@ -32,7 +27,7 @@ export class JumpStorm {
|
|||||||
].forEach((system) => this.game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
[new Floor(160), new Player()].forEach((entity) =>
|
[new Floor(160), new Player()].forEach((entity) =>
|
||||||
this.game.addEntity(entity)
|
this.game.addEntity(entity),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { loadAssets } from "@engine/config";
|
import { loadAssets } from "@engine/config";
|
||||||
import { JumpStorm} from "../Jumpstorm";
|
import { JumpStorm } from "../JumpStorm";
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
let ctx: CanvasRenderingContext2D;
|
let ctx: CanvasRenderingContext2D;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type LeaderBoardEntry } from "@engine/interfaces";
|
|
||||||
import LeaderBoardCard from "./LeaderBoardCard.svelte";
|
import LeaderBoardCard from "./LeaderBoardCard.svelte";
|
||||||
|
|
||||||
const MAX_ENTRIES = 8;
|
const MAX_ENTRIES = 8;
|
||||||
|
|
||||||
export let entries: LeaderBoardEntry[] = [];
|
export let entries: { name: string, score: number }[] = [];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="leaderboard">
|
<div class="leaderboard">
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type LeaderBoardEntry } from "@engine/interfaces";
|
export let entry = {
|
||||||
|
|
||||||
export let entry: LeaderBoardEntry = {
|
|
||||||
name: "simponic",
|
name: "simponic",
|
||||||
score: 100,
|
score: 100,
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,7 @@ export class Game {
|
|||||||
public componentEntities: Map<string, Set<number>>;
|
public componentEntities: Map<string, Set<number>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.lastTimeStamp = performance.now();
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this.systemOrder = [];
|
this.systemOrder = [];
|
||||||
this.systems = new Map();
|
this.systems = new Map();
|
||||||
@ -28,7 +29,7 @@ export class Game {
|
|||||||
this.entities.set(entity.id, entity);
|
this.entities.set(entity.id, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEntity(id: number): Entity {
|
public getEntity(id: number): Entity | undefined {
|
||||||
return this.entities.get(id);
|
return this.entities.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +37,18 @@ export class Game {
|
|||||||
this.entities.delete(id);
|
this.entities.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public forEachEntityWithComponent(
|
||||||
|
componentName: string,
|
||||||
|
callback: (entity: Entity) => void,
|
||||||
|
) {
|
||||||
|
this.componentEntities.get(componentName)?.forEach((entityId) => {
|
||||||
|
const entity = this.getEntity(entityId);
|
||||||
|
if (!entity) return;
|
||||||
|
|
||||||
|
callback(entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public addSystem(system: System) {
|
public addSystem(system: System) {
|
||||||
if (!this.systemOrder.includes(system.name)) {
|
if (!this.systemOrder.includes(system.name)) {
|
||||||
this.systemOrder.push(system.name);
|
this.systemOrder.push(system.name);
|
||||||
@ -43,7 +56,7 @@ export class Game {
|
|||||||
this.systems.set(system.name, system);
|
this.systems.set(system.name, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSystem(name: string): System {
|
public getSystem(name: string): System | undefined {
|
||||||
return this.systems.get(name);
|
return this.systems.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,16 +75,16 @@ export class Game {
|
|||||||
if (!this.componentEntities.has(component.name)) {
|
if (!this.componentEntities.has(component.name)) {
|
||||||
this.componentEntities.set(
|
this.componentEntities.set(
|
||||||
component.name,
|
component.name,
|
||||||
new Set<number>([entity.id])
|
new Set<number>([entity.id]),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.componentEntities.get(component.name).add(entity.id);
|
this.componentEntities.get(component.name)?.add(entity.id);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.systemOrder.forEach((systemName) => {
|
this.systemOrder.forEach((systemName) => {
|
||||||
this.systems.get(systemName).update(dt, this);
|
this.systems.get(systemName)?.update(dt, this);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, ComponentNames } from ".";
|
import { Component, ComponentNames } from ".";
|
||||||
import type { Coord2D, Dimension2D } from "../interfaces";
|
import type { Coord2D, Dimension2D } from "../interfaces";
|
||||||
import { dotProduct, rotateVector, normalizeVector } from "../utils";
|
import { dotProduct, rotateVector } from "../utils";
|
||||||
|
|
||||||
export class BoundingBox extends Component {
|
export class BoundingBox extends Component {
|
||||||
public center: Coord2D;
|
public center: Coord2D;
|
||||||
@ -15,10 +15,11 @@ export class BoundingBox extends Component {
|
|||||||
this.rotation = rotation ?? 0;
|
this.rotation = rotation ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
|
||||||
public isCollidingWith(box: BoundingBox): boolean {
|
public isCollidingWith(box: BoundingBox): boolean {
|
||||||
const boxes = [this.getVertices(), box.getVertices()];
|
const boxes = [this.getVertices(), box.getVertices()];
|
||||||
for (const poly of boxes) {
|
for (const poly of boxes) {
|
||||||
for (let i = 0; i < poly.length; ++i) {
|
for (let i = 0; i < poly.length; i++) {
|
||||||
const [A, B] = [poly[i], poly[(i + 1) % poly.length]];
|
const [A, B] = [poly[i], poly[(i + 1) % poly.length]];
|
||||||
const normal: Coord2D = { x: B.y - A.y, y: A.x - B.x };
|
const normal: Coord2D = { x: B.y - A.y, y: A.x - B.x };
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ export class BoundingBox extends Component {
|
|||||||
const projection = dotProduct(normal, vertex);
|
const projection = dotProduct(normal, vertex);
|
||||||
return [Math.min(min, projection), Math.max(max, projection)];
|
return [Math.min(min, projection), Math.max(max, projection)];
|
||||||
},
|
},
|
||||||
[Infinity, -Infinity]
|
[Infinity, -Infinity],
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (maxThis < minBox || maxBox < minThis) return false;
|
if (maxThis < minBox || maxBox < minThis) return false;
|
||||||
@ -55,43 +56,29 @@ export class BoundingBox extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAxes() {
|
public getRotationInPiOfUnitCircle() {
|
||||||
const corners: Coord2D[] = this.getVerticesRelativeToCenter();
|
let rads = this.rotation * (Math.PI / 180);
|
||||||
const axes: Coord2D[] = [];
|
if (rads >= Math.PI) {
|
||||||
|
rads -= Math.PI;
|
||||||
for (let i = 0; i < corners.length; ++i) {
|
}
|
||||||
const [cornerA, cornerB] = [
|
return rads;
|
||||||
corners[i],
|
|
||||||
corners[(i + 1) % corners.length],
|
|
||||||
].map((corner) => rotateVector(corner, this.rotation));
|
|
||||||
|
|
||||||
axes.push(
|
|
||||||
normalizeVector({
|
|
||||||
x: cornerB.y - cornerA.y,
|
|
||||||
y: -(cornerB.x - cornerA.x),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return axes;
|
public getOutscribedBoxDims(): Dimension2D {
|
||||||
}
|
let rads = this.getRotationInPiOfUnitCircle();
|
||||||
|
const { width, height } = this.dimension;
|
||||||
|
|
||||||
private project(axis: Coord2D): [number, number] {
|
if (rads <= Math.PI / 2) {
|
||||||
const corners = this.getCornersRelativeToCenter();
|
return {
|
||||||
let [min, max] = [Infinity, -Infinity];
|
width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)),
|
||||||
|
height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads)),
|
||||||
for (const corner of corners) {
|
|
||||||
const rotated = rotateVector(corner, this.rotation);
|
|
||||||
const translated = {
|
|
||||||
x: rotated.x + this.center.x,
|
|
||||||
y: rotated.y + this.center.y,
|
|
||||||
};
|
};
|
||||||
const projection = dotProduct(translated, axis);
|
|
||||||
|
|
||||||
min = Math.min(projection, min);
|
|
||||||
max = Math.max(projection, max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [min, max];
|
rads -= Math.PI / 2;
|
||||||
|
return {
|
||||||
|
width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)),
|
||||||
|
height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { Component, ComponentNames } from ".";
|
import { Component, ComponentNames, Velocity } from ".";
|
||||||
|
|
||||||
export class Control extends Component {
|
export class Control extends Component {
|
||||||
constructor() {
|
public controlVelocity: Velocity;
|
||||||
|
|
||||||
|
constructor(controlVelocity: Velocity = new Velocity()) {
|
||||||
super(ComponentNames.Control);
|
super(ComponentNames.Control);
|
||||||
|
|
||||||
|
this.controlVelocity = controlVelocity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Accel2D, Force2D } from "../interfaces";
|
import type { Force2D } from "../interfaces";
|
||||||
import { Component } from "./Component";
|
import { Component } from "./Component";
|
||||||
import { ComponentNames } from ".";
|
import { ComponentNames } from ".";
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export class Sprite extends Component {
|
|||||||
spriteImgPos: Coord2D,
|
spriteImgPos: Coord2D,
|
||||||
spriteImgDimensions: Dimension2D,
|
spriteImgDimensions: Dimension2D,
|
||||||
msPerFrame: number,
|
msPerFrame: number,
|
||||||
numFrames: number
|
numFrames: number,
|
||||||
) {
|
) {
|
||||||
super(ComponentNames.Sprite);
|
super(ComponentNames.Sprite);
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export class Sprite extends Component {
|
|||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(center.x, center.y);
|
ctx.translate(center.x, center.y);
|
||||||
if (rotation != 0) {
|
if (rotation != undefined && rotation != 0) {
|
||||||
ctx.rotate(rotation * (Math.PI / 180));
|
ctx.rotate(rotation * (Math.PI / 180));
|
||||||
}
|
}
|
||||||
ctx.translate(-center.x, -center.y);
|
ctx.translate(-center.x, -center.y);
|
||||||
@ -56,7 +56,7 @@ export class Sprite extends Component {
|
|||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
this.sheet,
|
this.sheet,
|
||||||
...this.getSpriteArgs(),
|
...this.getSpriteArgs(),
|
||||||
...this.getDrawArgs(drawArgs)
|
...this.getDrawArgs(drawArgs),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tint) {
|
if (tint) {
|
||||||
|
@ -6,10 +6,18 @@ export class Velocity extends Component {
|
|||||||
public dCartesian: Velocity2D;
|
public dCartesian: Velocity2D;
|
||||||
public dTheta: number;
|
public dTheta: number;
|
||||||
|
|
||||||
constructor(dCartesian: Velocity2D, dTheta: number) {
|
constructor(dCartesian: Velocity2D = { dx: 0, dy: 0 }, dTheta: number = 0) {
|
||||||
super(ComponentNames.Velocity);
|
super(ComponentNames.Velocity);
|
||||||
|
|
||||||
this.dCartesian = dCartesian;
|
this.dCartesian = dCartesian;
|
||||||
this.dTheta = dTheta;
|
this.dTheta = dTheta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public add(velocity?: Velocity) {
|
||||||
|
if (velocity) {
|
||||||
|
this.dCartesian.dx += velocity.dCartesian.dx;
|
||||||
|
this.dCartesian.dy += velocity.dCartesian.dy;
|
||||||
|
this.dTheta += velocity.dTheta;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { SPRITE_SPECS } from "./sprites";
|
|||||||
export const IMAGES = new Map<string, HTMLImageElement>();
|
export const IMAGES = new Map<string, HTMLImageElement>();
|
||||||
|
|
||||||
export const loadSpritesIntoImageElements = (
|
export const loadSpritesIntoImageElements = (
|
||||||
spriteSpecs: Partial<SpriteSpec>[]
|
spriteSpecs: Partial<SpriteSpec>[],
|
||||||
): Promise<void>[] => {
|
): Promise<void>[] => {
|
||||||
const spritePromises: Promise<void>[] = [];
|
const spritePromises: Promise<void>[] = [];
|
||||||
|
|
||||||
@ -17,13 +17,13 @@ export const loadSpritesIntoImageElements = (
|
|||||||
spritePromises.push(
|
spritePromises.push(
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
img.onload = () => resolve();
|
img.onload = () => resolve();
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spriteSpec.states) {
|
if (spriteSpec.states) {
|
||||||
spritePromises.push(
|
spritePromises.push(
|
||||||
...loadSpritesIntoImageElements(Object.values(spriteSpec.states))
|
...loadSpritesIntoImageElements(Array.from(spriteSpec.states.values())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +34,9 @@ export const loadSpritesIntoImageElements = (
|
|||||||
export const loadAssets = () =>
|
export const loadAssets = () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
...loadSpritesIntoImageElements(
|
...loadSpritesIntoImageElements(
|
||||||
Array.from(SPRITE_SPECS.keys()).map((key) => SPRITE_SPECS.get(key))
|
Array.from(SPRITE_SPECS.keys()).map(
|
||||||
|
(key) => SPRITE_SPECS.get(key) as SpriteSpec,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
// TODO: Sound
|
// TODO: Sound
|
||||||
]);
|
]);
|
||||||
|
@ -11,12 +11,12 @@ export namespace KeyConstants {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ActionKeys: Map<Action, string[]> = Object.keys(
|
export const ActionKeys: Map<Action, string[]> = Object.keys(
|
||||||
KeyActions
|
KeyActions,
|
||||||
).reduce((acc: Map<Action, string[]>, key) => {
|
).reduce((acc: Map<Action, string[]>, key) => {
|
||||||
const action = KeyActions[key];
|
const action = KeyActions[key];
|
||||||
|
|
||||||
if (acc.has(action)) {
|
if (acc.has(action)) {
|
||||||
acc.get(action).push(key);
|
acc.get(action)?.push(key);
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ export namespace PhysicsConstants {
|
|||||||
export const MAX_JUMP_TIME_MS = 150;
|
export const MAX_JUMP_TIME_MS = 150;
|
||||||
export const GRAVITY = 0.0075;
|
export const GRAVITY = 0.0075;
|
||||||
export const PLAYER_MOVE_VEL = 1;
|
export const PLAYER_MOVE_VEL = 1;
|
||||||
export const PLAYER_JUMP_ACC = -0.01;
|
export const PLAYER_JUMP_ACC = -0.008;
|
||||||
export const PLAYER_JUMP_INITIAL_VEL = -0.9;
|
export const PLAYER_JUMP_INITIAL_VEL = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Miscellaneous {
|
export namespace Miscellaneous {
|
||||||
|
@ -10,7 +10,7 @@ export interface SpriteSpec {
|
|||||||
height: number;
|
height: number;
|
||||||
frames: number;
|
frames: number;
|
||||||
msPerFrame: number;
|
msPerFrame: number;
|
||||||
states?: Record<string | number, Partial<SpriteSpec>>;
|
states?: Map<string | number, Partial<SpriteSpec>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SPRITE_SPECS: Map<Sprites, Partial<SpriteSpec>> = new Map<
|
export const SPRITE_SPECS: Map<Sprites, Partial<SpriteSpec>> = new Map<
|
||||||
@ -22,28 +22,27 @@ const floorSpriteSpec = {
|
|||||||
height: 40,
|
height: 40,
|
||||||
frames: 3,
|
frames: 3,
|
||||||
msPerFrame: 125,
|
msPerFrame: 125,
|
||||||
states: {},
|
states: new Map<number, Partial<SpriteSpec>>(),
|
||||||
};
|
};
|
||||||
floorSpriteSpec.states = [40, 80, 120, 160].reduce((acc, cur) => {
|
[40, 80, 120, 160].forEach((width) => {
|
||||||
acc[cur] = {
|
floorSpriteSpec.states.set(width, {
|
||||||
width: cur,
|
width,
|
||||||
sheet: `/assets/floor_tile_${cur}.png`,
|
sheet: `/assets/floor_tile_${width}.png`,
|
||||||
};
|
});
|
||||||
return acc;
|
});
|
||||||
}, {});
|
|
||||||
SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec);
|
SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec);
|
||||||
|
|
||||||
SPRITE_SPECS.set(Sprites.COFFEE, {
|
const coffeeSpriteSpec = {
|
||||||
msPerFrame: 100,
|
msPerFrame: 100,
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 45,
|
height: 45,
|
||||||
frames: 3,
|
frames: 3,
|
||||||
states: {
|
states: new Map<string, Partial<SpriteSpec>>(),
|
||||||
LEFT: {
|
};
|
||||||
|
coffeeSpriteSpec.states.set("LEFT", {
|
||||||
sheet: "/assets/coffee_left.png",
|
sheet: "/assets/coffee_left.png",
|
||||||
},
|
|
||||||
RIGHT: {
|
|
||||||
sheet: "/assets/coffee_right.png",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
coffeeSpriteSpec.states.set("RIGHT", {
|
||||||
|
sheet: "/assets/coffee_right.png",
|
||||||
|
});
|
||||||
|
SPRITE_SPECS.set(Sprites.COFFEE, coffeeSpriteSpec);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { Component } from "../components";
|
import type { Component } from "../components";
|
||||||
import { ComponentNotFoundError } from "../exceptions";
|
|
||||||
|
|
||||||
export abstract class Entity {
|
export abstract class Entity {
|
||||||
private static ID = 0;
|
private static ID = 0;
|
||||||
|
@ -4,26 +4,28 @@ import { TopCollidable } from "../components/TopCollidable";
|
|||||||
import { Entity } from "../entities";
|
import { Entity } from "../entities";
|
||||||
|
|
||||||
export class Floor extends Entity {
|
export class Floor extends Entity {
|
||||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(Sprites.FLOOR);
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||||
|
Sprites.FLOOR,
|
||||||
|
) as SpriteSpec;
|
||||||
|
|
||||||
constructor(width: number) {
|
constructor(width: number) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addComponent(
|
this.addComponent(
|
||||||
new Sprite(
|
new Sprite(
|
||||||
IMAGES.get(Floor.spriteSpec.states[width].sheet),
|
IMAGES.get((Floor.spriteSpec?.states?.get(width) as SpriteSpec).sheet),
|
||||||
{ x: 0, y: 0 },
|
{ x: 0, y: 0 },
|
||||||
{ width, height: Floor.spriteSpec.height },
|
{ width, height: Floor.spriteSpec.height },
|
||||||
Floor.spriteSpec.msPerFrame,
|
Floor.spriteSpec.msPerFrame,
|
||||||
Floor.spriteSpec.frames
|
Floor.spriteSpec.frames,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addComponent(
|
this.addComponent(
|
||||||
new BoundingBox(
|
new BoundingBox(
|
||||||
{ x: 300, y: 300 },
|
{ x: 300, y: 300 },
|
||||||
{ width, height: Floor.spriteSpec.height }
|
{ width, height: Floor.spriteSpec.height },
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addComponent(new TopCollidable());
|
this.addComponent(new TopCollidable());
|
||||||
|
@ -14,14 +14,15 @@ import {
|
|||||||
Mass,
|
Mass,
|
||||||
Moment,
|
Moment,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { PhysicsConstants } from "../config";
|
|
||||||
import { Direction } from "../interfaces";
|
import { Direction } from "../interfaces";
|
||||||
|
|
||||||
export class Player extends Entity {
|
export class Player extends Entity {
|
||||||
private static MASS: number = 10;
|
private static MASS: number = 10;
|
||||||
private static MOI: number = 1000;
|
private static MOI: number = 1000;
|
||||||
|
|
||||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(Sprites.COFFEE);
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||||
|
Sprites.COFFEE,
|
||||||
|
) as SpriteSpec;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -30,8 +31,8 @@ export class Player extends Entity {
|
|||||||
new BoundingBox(
|
new BoundingBox(
|
||||||
{ x: 300, y: 100 },
|
{ x: 300, y: 100 },
|
||||||
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
|
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
|
||||||
0
|
0,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addComponent(new Velocity({ dx: 0, dy: 0 }, 0));
|
this.addComponent(new Velocity({ dx: 0, dy: 0 }, 0));
|
||||||
@ -54,12 +55,12 @@ export class Player extends Entity {
|
|||||||
const [leftSprite, rightSprite] = [Direction.LEFT, Direction.RIGHT].map(
|
const [leftSprite, rightSprite] = [Direction.LEFT, Direction.RIGHT].map(
|
||||||
(direction) =>
|
(direction) =>
|
||||||
new Sprite(
|
new Sprite(
|
||||||
IMAGES.get(Player.spriteSpec.states[direction].sheet),
|
IMAGES.get(Player.spriteSpec.states?.get(direction)?.sheet as string),
|
||||||
{ x: 0, y: 0 },
|
{ x: 0, y: 0 },
|
||||||
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
|
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
|
||||||
Player.spriteSpec.msPerFrame,
|
Player.spriteSpec.msPerFrame,
|
||||||
Player.spriteSpec.frames
|
Player.spriteSpec.frames,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addComponent(new FacingDirection(leftSprite, rightSprite));
|
this.addComponent(new FacingDirection(leftSprite, rightSprite));
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export interface LeaderBoardEntry {
|
|
||||||
name: string;
|
|
||||||
score: number;
|
|
||||||
avatar: string;
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
export * from "./LeaderBoardEntry";
|
|
||||||
export * from "./Vec2";
|
export * from "./Vec2";
|
||||||
export * from "./Draw";
|
export * from "./Draw";
|
||||||
export * from "./Direction";
|
export * from "./Direction";
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import type { Coord2D, Dimension2D } from "../interfaces";
|
import type { Coord2D, Dimension2D } from "../interfaces";
|
||||||
import { ComponentNames, BoundingBox } from "../components";
|
|
||||||
import { Entity } from "../entities";
|
|
||||||
|
|
||||||
interface BoxedEntry {
|
interface BoxedEntry {
|
||||||
id: number;
|
id: number;
|
||||||
@ -30,21 +28,26 @@ export class QuadTree {
|
|||||||
dimension: Dimension2D,
|
dimension: Dimension2D,
|
||||||
maxLevels: number,
|
maxLevels: number,
|
||||||
splitThreshold: number,
|
splitThreshold: number,
|
||||||
level?: number
|
level?: number,
|
||||||
) {
|
) {
|
||||||
this.children = [];
|
this.children = new Map<Quadrant, QuadTree>();
|
||||||
this.objects = [];
|
this.objects = [];
|
||||||
|
|
||||||
this.maxLevels = maxLevels;
|
this.maxLevels = maxLevels;
|
||||||
this.splitThreshold = splitThreshold;
|
this.splitThreshold = splitThreshold;
|
||||||
this.level = level ?? 0;
|
this.level = level ?? 0;
|
||||||
|
|
||||||
|
this.topLeft = topLeft;
|
||||||
|
this.dimension = dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
public insert(id: number, dimension: Dimension2D, center: Coord2D): void {
|
public insert(id: number, dimension: Dimension2D, center: Coord2D): void {
|
||||||
|
const box: BoxedEntry = { id, center, dimension };
|
||||||
if (this.hasChildren()) {
|
if (this.hasChildren()) {
|
||||||
this.getIndices(boundingBox).forEach((i) =>
|
this.getQuadrants(box).forEach((quadrant) => {
|
||||||
this.children[i].insert(id, dimension, center)
|
const quadrantBox = this.children.get(quadrant);
|
||||||
);
|
quadrantBox?.insert(id, dimension, center);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +77,10 @@ export class QuadTree {
|
|||||||
|
|
||||||
if (this.hasChildren()) {
|
if (this.hasChildren()) {
|
||||||
this.getQuadrants(boxedEntry).forEach((quadrant) => {
|
this.getQuadrants(boxedEntry).forEach((quadrant) => {
|
||||||
this.children
|
const quadrantBox = this.children.get(quadrant);
|
||||||
.get(quadrant)
|
|
||||||
.getNeighborIds(boxedEntry)
|
quadrantBox
|
||||||
|
?.getNeighborIds(boxedEntry)
|
||||||
.forEach((id) => neighbors.push(id));
|
.forEach((id) => neighbors.push(id));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -88,6 +92,7 @@ export class QuadTree {
|
|||||||
const halfWidth = this.dimension.width / 2;
|
const halfWidth = this.dimension.width / 2;
|
||||||
const halfHeight = this.dimension.height / 2;
|
const halfHeight = this.dimension.height / 2;
|
||||||
|
|
||||||
|
(
|
||||||
[
|
[
|
||||||
[Quadrant.I, { x: this.topLeft.x + halfWidth, y: this.topLeft.y }],
|
[Quadrant.I, { x: this.topLeft.x + halfWidth, y: this.topLeft.y }],
|
||||||
[Quadrant.II, { ...this.topLeft }],
|
[Quadrant.II, { ...this.topLeft }],
|
||||||
@ -96,7 +101,8 @@ export class QuadTree {
|
|||||||
Quadrant.IV,
|
Quadrant.IV,
|
||||||
{ x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight },
|
{ x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight },
|
||||||
],
|
],
|
||||||
].forEach(([quadrant, pos]) => {
|
] as [[Quadrant, Coord2D]]
|
||||||
|
).forEach(([quadrant, pos]) => {
|
||||||
this.children.set(
|
this.children.set(
|
||||||
quadrant,
|
quadrant,
|
||||||
new QuadTree(
|
new QuadTree(
|
||||||
@ -104,34 +110,48 @@ export class QuadTree {
|
|||||||
{ width: halfWidth, height: halfHeight },
|
{ width: halfWidth, height: halfHeight },
|
||||||
this.maxLevels,
|
this.maxLevels,
|
||||||
this.splitThreshold,
|
this.splitThreshold,
|
||||||
this.level + 1
|
this.level + 1,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getQuandrants(boxedEntry: BoxedEntry): Quadrant[] {
|
private getQuadrants(boxedEntry: BoxedEntry): Quadrant[] {
|
||||||
const treeCenter: Coord2D = {
|
const treeCenter: Coord2D = {
|
||||||
x: this.topLeft.x + this.dimension.width / 2,
|
x: this.topLeft.x + this.dimension.width / 2,
|
||||||
y: this.topLeft.y + this.dimension.height / 2,
|
y: this.topLeft.y + this.dimension.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return (
|
||||||
[Quadrant.I, (x, y) => x >= treeCenter.x && y < treeCenter.y],
|
[
|
||||||
[Quadrant.II, (x, y) => x < treeCenter.x && y < treeCenter.y],
|
[
|
||||||
[Quadrant.III, (x, y) => x < treeCenter.x && y >= treeCenter.y],
|
Quadrant.I,
|
||||||
[Quadrant.IV, (x, y) => x >= treeCenter.x && y >= treeCenter.y],
|
(x: number, y: number) => x >= treeCenter.x && y < treeCenter.y,
|
||||||
]
|
],
|
||||||
|
[
|
||||||
|
Quadrant.II,
|
||||||
|
(x: number, y: number) => x < treeCenter.x && y < treeCenter.y,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Quadrant.III,
|
||||||
|
(x: number, y: number) => x < treeCenter.x && y >= treeCenter.y,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Quadrant.IV,
|
||||||
|
(x: number, y: number) => x >= treeCenter.x && y >= treeCenter.y,
|
||||||
|
],
|
||||||
|
] as [[Quadrant, (x: number, y: number) => boolean]]
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
([_quadrant, condition]) =>
|
([_quadrant, condition]) =>
|
||||||
condition(
|
condition(
|
||||||
boxedEntry.center.x + boxedEntry.dimension.width / 2,
|
boxedEntry.center.x + boxedEntry.dimension.width / 2,
|
||||||
boxedEntry.center.y + boxedEntry.dimension.height / 2
|
boxedEntry.center.y + boxedEntry.dimension.height / 2,
|
||||||
) ||
|
) ||
|
||||||
condition(
|
condition(
|
||||||
boxedEntry.center.x - boxedEntry.dimension.width / 2,
|
boxedEntry.center.x - boxedEntry.dimension.width / 2,
|
||||||
boxedEntry.center.y - boxedEntry.dimension.height / 2
|
boxedEntry.center.y - boxedEntry.dimension.height / 2,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.map(([quadrant]) => quadrant);
|
.map(([quadrant]) => quadrant);
|
||||||
}
|
}
|
||||||
@ -139,9 +159,12 @@ export class QuadTree {
|
|||||||
private realignObjects(): void {
|
private realignObjects(): void {
|
||||||
this.objects.forEach((boxedEntry) => {
|
this.objects.forEach((boxedEntry) => {
|
||||||
this.getQuadrants(boxedEntry).forEach((direction) => {
|
this.getQuadrants(boxedEntry).forEach((direction) => {
|
||||||
this.children
|
const quadrant = this.children.get(direction);
|
||||||
.get(direction)
|
quadrant?.insert(
|
||||||
.insert(boxedEntry.id, boxedEntry.dimension, boxedEntry.center);
|
boxedEntry.id,
|
||||||
|
boxedEntry.dimension,
|
||||||
|
boxedEntry.center,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -149,6 +172,6 @@ export class QuadTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hasChildren() {
|
private hasChildren() {
|
||||||
return this.children && this.children.length > 0;
|
return this.children && this.children.size > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
ComponentNames,
|
ComponentNames,
|
||||||
Jump,
|
Jump,
|
||||||
Velocity,
|
Velocity,
|
||||||
Moment,
|
Forces,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
import { PhysicsConstants } from "../config";
|
import { PhysicsConstants } from "../config";
|
||||||
@ -30,60 +30,64 @@ export class Collision extends System {
|
|||||||
{ x: 0, y: 0 },
|
{ x: 0, y: 0 },
|
||||||
screenDimensions,
|
screenDimensions,
|
||||||
Collision.QUADTREE_MAX_LEVELS,
|
Collision.QUADTREE_MAX_LEVELS,
|
||||||
Collision.QUADTREE_SPLIT_THRESHOLD
|
Collision.QUADTREE_SPLIT_THRESHOLD,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(dt: number, game: Game) {
|
public update(_dt: number, game: Game) {
|
||||||
// rebuild the quadtree
|
// rebuild the quadtree
|
||||||
this.quadTree.clear();
|
this.quadTree.clear();
|
||||||
|
|
||||||
const entitiesToAddToQuadtree: Entity[] = [];
|
const entitiesToAddToQuadtree: Entity[] = [];
|
||||||
|
|
||||||
Collision.COLLIDABLE_COMPONENT_NAMES.map((componentName) =>
|
Collision.COLLIDABLE_COMPONENT_NAMES.map((componentName) =>
|
||||||
game.componentEntities.get(componentName)
|
game.componentEntities.get(componentName),
|
||||||
).forEach((entityIds?: Set<number>) =>
|
).forEach(
|
||||||
|
(entityIds?: Set<number>) =>
|
||||||
entityIds?.forEach((id) => {
|
entityIds?.forEach((id) => {
|
||||||
const entity = game.entities.get(id);
|
const entity = game.entities.get(id);
|
||||||
if (!entity.hasComponent(ComponentNames.BoundingBox)) {
|
if (!entity || !entity.hasComponent(ComponentNames.BoundingBox)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entitiesToAddToQuadtree.push(entity);
|
entitiesToAddToQuadtree.push(entity);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
entitiesToAddToQuadtree.forEach((entity) => {
|
entitiesToAddToQuadtree.forEach((entity) => {
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox
|
ComponentNames.BoundingBox,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.quadTree.insert(
|
let dimension = { ...boundingBox.dimension };
|
||||||
entity.id,
|
if (boundingBox.rotation != 0) {
|
||||||
boundingBox.dimension,
|
dimension = boundingBox.getOutscribedBoxDims();
|
||||||
boundingBox.center
|
}
|
||||||
);
|
|
||||||
|
this.quadTree.insert(entity.id, dimension, boundingBox.center);
|
||||||
});
|
});
|
||||||
|
|
||||||
// find colliding entities and perform collisions
|
// find colliding entities and perform collisions
|
||||||
const collidingEntities = this.getCollidingEntities(
|
const collidingEntities = this.getCollidingEntities(
|
||||||
entitiesToAddToQuadtree,
|
entitiesToAddToQuadtree,
|
||||||
game.entities
|
game,
|
||||||
);
|
);
|
||||||
|
|
||||||
collidingEntities.forEach(([entityAId, entityBId]) => {
|
collidingEntities.forEach(([entityAId, entityBId]) => {
|
||||||
const [entityA, entityB] = [entityAId, entityBId].map((id) =>
|
const [entityA, entityB] = [entityAId, entityBId].map((id) =>
|
||||||
game.entities.get(id)
|
game.entities.get(id),
|
||||||
);
|
);
|
||||||
|
if (entityA && entityB) {
|
||||||
this.performCollision(entityA, entityB);
|
this.performCollision(entityA, entityB);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private performCollision(entityA: Entity, entityB: Entity) {
|
private performCollision(entityA: Entity, entityB: Entity) {
|
||||||
const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map(
|
const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map(
|
||||||
(entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox)
|
(entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox),
|
||||||
);
|
);
|
||||||
|
|
||||||
let velocity: Velocity;
|
let velocity = new Velocity();
|
||||||
if (entityA.hasComponent(ComponentNames.Velocity)) {
|
if (entityA.hasComponent(ComponentNames.Velocity)) {
|
||||||
velocity = entityA.getComponent<Velocity>(ComponentNames.Velocity);
|
velocity = entityA.getComponent<Velocity>(ComponentNames.Velocity);
|
||||||
}
|
}
|
||||||
@ -92,17 +96,16 @@ export class Collision extends System {
|
|||||||
entityA.hasComponent(ComponentNames.Collide) &&
|
entityA.hasComponent(ComponentNames.Collide) &&
|
||||||
entityB.hasComponent(ComponentNames.TopCollidable) &&
|
entityB.hasComponent(ComponentNames.TopCollidable) &&
|
||||||
entityABoundingBox.center.y <= entityBBoundingBox.center.y &&
|
entityABoundingBox.center.y <= entityBBoundingBox.center.y &&
|
||||||
velocity &&
|
|
||||||
velocity.dCartesian.dy >= 0 // don't apply "floor" logic when coming through the bottom
|
velocity.dCartesian.dy >= 0 // don't apply "floor" logic when coming through the bottom
|
||||||
) {
|
) {
|
||||||
if (entityBBoundingBox.rotation != 0) {
|
if (entityBBoundingBox.rotation != 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`
|
`entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove previous velocity in the y axis
|
// remove previous velocity in the y axis
|
||||||
velocity.dCartesian.dy = 0;
|
if (velocity) velocity.dCartesian.dy = 0;
|
||||||
|
|
||||||
// apply normal force
|
// apply normal force
|
||||||
if (entityA.hasComponent(ComponentNames.Gravity)) {
|
if (entityA.hasComponent(ComponentNames.Gravity)) {
|
||||||
@ -110,7 +113,8 @@ export class Collision extends System {
|
|||||||
const F_n = -mass * PhysicsConstants.GRAVITY;
|
const F_n = -mass * PhysicsConstants.GRAVITY;
|
||||||
|
|
||||||
entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({
|
entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({
|
||||||
fCartesian: { fy: F_n },
|
fCartesian: { fy: F_n, fx: 0 },
|
||||||
|
torque: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,26 +132,30 @@ export class Collision extends System {
|
|||||||
|
|
||||||
private getCollidingEntities(
|
private getCollidingEntities(
|
||||||
collidableEntities: Entity[],
|
collidableEntities: Entity[],
|
||||||
entityMap: Map<number, Entity>
|
game: Game,
|
||||||
): [number, number][] {
|
): [number, number][] {
|
||||||
const collidingEntityIds: [number, number] = [];
|
const collidingEntityIds: [number, number][] = [];
|
||||||
|
|
||||||
for (const entity of collidableEntities) {
|
for (const entity of collidableEntities) {
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox
|
ComponentNames.BoundingBox,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.quadTree
|
const neighborIds = this.quadTree
|
||||||
.getNeighborIds({
|
.getNeighborIds({
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
dimension: boundingBox.dimension,
|
dimension: boundingBox.dimension,
|
||||||
center: boundingBox.center,
|
center: boundingBox.center,
|
||||||
})
|
})
|
||||||
.filter((neighborId) => neighborId != entity.id)
|
.filter((neighborId) => neighborId != entity.id);
|
||||||
.forEach((neighborId) => {
|
|
||||||
const neighborBoundingBox = entityMap
|
neighborIds.forEach((neighborId) => {
|
||||||
.get(neighborId)
|
const neighbor = game.getEntity(neighborId);
|
||||||
.getComponent<BoundingBox>(ComponentNames.BoundingBox);
|
if (!neighbor) return;
|
||||||
|
|
||||||
|
const neighborBoundingBox = neighbor.getComponent<BoundingBox>(
|
||||||
|
ComponentNames.BoundingBox,
|
||||||
|
);
|
||||||
|
|
||||||
if (boundingBox.isCollidingWith(neighborBoundingBox)) {
|
if (boundingBox.isCollidingWith(neighborBoundingBox)) {
|
||||||
collidingEntityIds.push([entity.id, neighborId]);
|
collidingEntityIds.push([entity.id, neighborId]);
|
||||||
@ -161,55 +169,45 @@ export class Collision extends System {
|
|||||||
// ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ
|
// ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ
|
||||||
private getDyToPushOutOfFloor(
|
private getDyToPushOutOfFloor(
|
||||||
entityBoundingBox: BoundingBox,
|
entityBoundingBox: BoundingBox,
|
||||||
floorBoundingBox: BoundingBox
|
floorBoundingBox: BoundingBox,
|
||||||
): number {
|
): number {
|
||||||
const {
|
const {
|
||||||
rotation,
|
|
||||||
center: { x, y },
|
|
||||||
dimension: { width, height },
|
dimension: { width, height },
|
||||||
|
center: { x },
|
||||||
} = entityBoundingBox;
|
} = entityBoundingBox;
|
||||||
|
|
||||||
let rads = rotation * (Math.PI / 180);
|
const outScribedRectangle = entityBoundingBox.getOutscribedBoxDims();
|
||||||
if (rads >= Math.PI) {
|
|
||||||
rads -= Math.PI; // we have symmetry so we can skip two cases
|
|
||||||
}
|
|
||||||
|
|
||||||
let boundedCollisionX = 0; // bounded x on the surface from width
|
let rads = entityBoundingBox.getRotationInPiOfUnitCircle();
|
||||||
let clippedX = 0; // x coordinate of the vertex below the surface
|
let dx = (width * Math.cos(rads) - height * Math.sin(rads)) / 2;
|
||||||
let outScribedRectangleHeight, dy, dx;
|
|
||||||
|
|
||||||
if (rads <= Math.PI / 2) {
|
if (rads >= Math.PI / 2) {
|
||||||
dx = (width * Math.cos(rads) - height * Math.sin(rads)) / 2;
|
|
||||||
outScribedRectangleHeight =
|
|
||||||
width * Math.sin(rads) + height * Math.cos(rads);
|
|
||||||
} else if (rads <= Math.PI) {
|
|
||||||
rads -= Math.PI / 2;
|
rads -= Math.PI / 2;
|
||||||
dx = (height * Math.cos(rads) - width * Math.sin(rads)) / 2;
|
dx = (height * Math.cos(rads) - width * Math.sin(rads)) / 2;
|
||||||
outScribedRectangleHeight =
|
|
||||||
width * Math.cos(rads) + height * Math.sin(rads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clippedX = x + dx; // x coordinate of the vertex below the surface (if existant)
|
||||||
|
let boundedCollisionX = 0; // bounded x on the surface from width
|
||||||
|
|
||||||
if (x >= floorBoundingBox.center.x) {
|
if (x >= floorBoundingBox.center.x) {
|
||||||
clippedX = x + dx;
|
|
||||||
boundedCollisionX = Math.min(
|
boundedCollisionX = Math.min(
|
||||||
floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2,
|
floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2,
|
||||||
clippedX
|
clippedX,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
outScribedRectangleHeight / 2 -
|
outScribedRectangle.height / 2 -
|
||||||
Math.max((clippedX - boundedCollisionX) * Math.tan(rads), 0)
|
Math.max((clippedX - boundedCollisionX) * Math.tan(rads), 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
clippedX = x - dx;
|
|
||||||
boundedCollisionX = Math.max(
|
boundedCollisionX = Math.max(
|
||||||
floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2,
|
floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2,
|
||||||
clippedX
|
clippedX,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
outScribedRectangleHeight / 2 -
|
outScribedRectangle.height / 2 -
|
||||||
Math.max((boundedCollisionX - clippedX) * Math.tan(rads), 0)
|
Math.max((boundedCollisionX - clippedX) * Math.tan(Math.PI / 2 - rads), 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ import {
|
|||||||
ComponentNames,
|
ComponentNames,
|
||||||
Velocity,
|
Velocity,
|
||||||
FacingDirection as FacingDirectionComponent,
|
FacingDirection as FacingDirectionComponent,
|
||||||
|
Control,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
import type { Entity } from "../entities";
|
|
||||||
import { System, SystemNames } from "./";
|
import { System, SystemNames } from "./";
|
||||||
|
|
||||||
export class FacingDirection extends System {
|
export class FacingDirection extends System {
|
||||||
@ -13,24 +13,31 @@ export class FacingDirection extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public update(_dt: number, game: Game) {
|
public update(_dt: number, game: Game) {
|
||||||
game.componentEntities
|
game.forEachEntityWithComponent(
|
||||||
.get(ComponentNames.FacingDirection)
|
ComponentNames.FacingDirection,
|
||||||
?.forEach((entityId) => {
|
(entity) => {
|
||||||
const entity = game.entities.get(entityId);
|
|
||||||
if (!entity.hasComponent(ComponentNames.Velocity)) {
|
if (!entity.hasComponent(ComponentNames.Velocity)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalVelocity: Velocity = new Velocity();
|
||||||
|
const control = entity.getComponent<Control>(ComponentNames.Control);
|
||||||
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
||||||
|
totalVelocity.add(velocity);
|
||||||
|
if (control) {
|
||||||
|
totalVelocity.add(control.controlVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
const facingDirection = entity.getComponent<FacingDirectionComponent>(
|
const facingDirection = entity.getComponent<FacingDirectionComponent>(
|
||||||
ComponentNames.FacingDirection
|
ComponentNames.FacingDirection,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (velocity.dCartesian.dx > 0) {
|
if (totalVelocity.dCartesian.dx > 0) {
|
||||||
entity.addComponent(facingDirection.facingRightSprite);
|
entity.addComponent(facingDirection.facingRightSprite);
|
||||||
} else if (velocity.dCartesian.dx < 0) {
|
} else if (totalVelocity.dCartesian.dx < 0) {
|
||||||
entity.addComponent(facingDirection.facingLeftSprite);
|
entity.addComponent(facingDirection.facingLeftSprite);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
Jump,
|
Jump,
|
||||||
Forces,
|
Forces,
|
||||||
Acceleration,
|
|
||||||
ComponentNames,
|
ComponentNames,
|
||||||
Velocity,
|
Velocity,
|
||||||
Mass,
|
Mass,
|
||||||
|
Control,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
import { KeyConstants, PhysicsConstants } from "../config";
|
import { KeyConstants, PhysicsConstants } from "../config";
|
||||||
import type { Entity } from "../entities";
|
|
||||||
import { Action } from "../interfaces";
|
import { Action } from "../interfaces";
|
||||||
import { System, SystemNames } from "./";
|
import { System, SystemNames } from "./";
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Make velocities reset on each game loop (as similar to acceleration)
|
|
||||||
* - Then, we can add / remove velocity on update instead of just setting it and praying it's not modified externally
|
|
||||||
*/
|
|
||||||
export class Input extends System {
|
export class Input extends System {
|
||||||
private keys: Set<string>;
|
private keys: Set<string>;
|
||||||
private actionTimeStamps: Map<Action, number>;
|
private actionTimeStamps: Map<Action, number>;
|
||||||
@ -23,7 +18,7 @@ export class Input extends System {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(SystemNames.Input);
|
super(SystemNames.Input);
|
||||||
|
|
||||||
this.keys = new Set<number>();
|
this.keys = new Set<string>();
|
||||||
this.actionTimeStamps = new Map<Action, number>();
|
this.actionTimeStamps = new Map<Action, number>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,53 +30,54 @@ export class Input extends System {
|
|||||||
this.keys.delete(key);
|
this.keys.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasSomeKey(keys: string[]): boolean {
|
private hasSomeKey(keys?: string[]): boolean {
|
||||||
|
if (keys) {
|
||||||
return keys.some((key) => this.keys.has(key));
|
return keys.some((key) => this.keys.has(key));
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
public update(dt: number, game: Game) {
|
|
||||||
game.componentEntities.get(ComponentNames.Control)?.forEach((entityId) => {
|
|
||||||
const entity = game.entities.get(entityId);
|
|
||||||
if (!entity.hasComponent(ComponentNames.Velocity)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
public update(_dt: number, game: Game) {
|
||||||
|
game.forEachEntityWithComponent(ComponentNames.Control, (entity) => {
|
||||||
|
const control = entity.getComponent<Control>(ComponentNames.Control);
|
||||||
|
|
||||||
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_RIGHT))) {
|
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_RIGHT))) {
|
||||||
velocity.dCartesian.dx = PhysicsConstants.PLAYER_MOVE_VEL;
|
control.controlVelocity.dCartesian.dx +=
|
||||||
} else if (
|
PhysicsConstants.PLAYER_MOVE_VEL;
|
||||||
this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))
|
|
||||||
) {
|
|
||||||
velocity.dCartesian.dx = -PhysicsConstants.PLAYER_MOVE_VEL;
|
|
||||||
} else {
|
|
||||||
velocity.dCartesian.dx = 0;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
game.componentEntities.get(ComponentNames.Jump)?.forEach((entityId) => {
|
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))) {
|
||||||
const entity = game.entities.get(entityId);
|
control.controlVelocity.dCartesian.dx +=
|
||||||
const jump = entity.getComponent<Jump>(ComponentNames.Jump);
|
-PhysicsConstants.PLAYER_MOVE_VEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.hasComponent(ComponentNames.Jump)) {
|
||||||
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
||||||
|
const jump = entity.getComponent<Jump>(ComponentNames.Jump);
|
||||||
|
|
||||||
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) {
|
if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) {
|
||||||
if (jump.canJump) {
|
if (jump.canJump) {
|
||||||
this.actionTimeStamps.set(Action.JUMP, performance.now());
|
this.actionTimeStamps.set(Action.JUMP, performance.now());
|
||||||
|
|
||||||
velocity.dCartesian.dy = PhysicsConstants.PLAYER_JUMP_INITIAL_VEL;
|
velocity.dCartesian.dy += PhysicsConstants.PLAYER_JUMP_INITIAL_VEL;
|
||||||
jump.canJump = false;
|
jump.canJump = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
performance.now() - this.actionTimeStamps.get(Action.JUMP) <
|
performance.now() - (this.actionTimeStamps.get(Action.JUMP) || 0) <
|
||||||
PhysicsConstants.MAX_JUMP_TIME_MS
|
PhysicsConstants.MAX_JUMP_TIME_MS
|
||||||
) {
|
) {
|
||||||
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
|
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
|
||||||
entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({
|
entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({
|
||||||
fCartesian: { fy: mass * PhysicsConstants.PLAYER_JUMP_ACC },
|
fCartesian: {
|
||||||
|
fy: mass * PhysicsConstants.PLAYER_JUMP_ACC,
|
||||||
|
fx: 0,
|
||||||
|
},
|
||||||
|
torque: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { System, SystemNames } from ".";
|
import { System, SystemNames } from ".";
|
||||||
import {
|
import {
|
||||||
Acceleration,
|
|
||||||
BoundingBox,
|
BoundingBox,
|
||||||
ComponentNames,
|
ComponentNames,
|
||||||
Forces,
|
Forces,
|
||||||
@ -8,9 +7,10 @@ import {
|
|||||||
Velocity,
|
Velocity,
|
||||||
Mass,
|
Mass,
|
||||||
Jump,
|
Jump,
|
||||||
|
Moment,
|
||||||
|
Control,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { PhysicsConstants } from "../config";
|
import { PhysicsConstants } from "../config";
|
||||||
import type { Entity } from "../entities";
|
|
||||||
import type { Force2D } from "../interfaces";
|
import type { Force2D } from "../interfaces";
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
|
|
||||||
@ -20,14 +20,12 @@ export class Physics extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public update(dt: number, game: Game): void {
|
public update(dt: number, game: Game): void {
|
||||||
game.componentEntities.get(ComponentNames.Forces)?.forEach((entityId) => {
|
game.forEachEntityWithComponent(ComponentNames.Forces, (entity) => {
|
||||||
const entity = game.entities.get(entityId);
|
|
||||||
|
|
||||||
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
|
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
|
||||||
const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces;
|
const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces;
|
||||||
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
||||||
const inertia = entity.getComponent<Moment>(
|
const inertia = entity.getComponent<Moment>(
|
||||||
ComponentNames.Moment
|
ComponentNames.Moment,
|
||||||
).inertia;
|
).inertia;
|
||||||
|
|
||||||
// F_g = mg, applied only until terminal velocity is reached
|
// F_g = mg, applied only until terminal velocity is reached
|
||||||
@ -37,7 +35,9 @@ export class Physics extends System {
|
|||||||
forces.push({
|
forces.push({
|
||||||
fCartesian: {
|
fCartesian: {
|
||||||
fy: mass * PhysicsConstants.GRAVITY,
|
fy: mass * PhysicsConstants.GRAVITY,
|
||||||
|
fx: 0,
|
||||||
},
|
},
|
||||||
|
torque: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ export class Physics extends System {
|
|||||||
},
|
},
|
||||||
torque: accum.torque + (torque ?? 0),
|
torque: accum.torque + (torque ?? 0),
|
||||||
}),
|
}),
|
||||||
{ fCartesian: { fx: 0, fy: 0 }, torque: 0 }
|
{ fCartesian: { fx: 0, fy: 0 }, torque: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
// integrate accelerations
|
// integrate accelerations
|
||||||
@ -62,6 +62,7 @@ export class Physics extends System {
|
|||||||
velocity.dCartesian.dx += ddx * dt;
|
velocity.dCartesian.dx += ddx * dt;
|
||||||
velocity.dCartesian.dy += ddy * dt;
|
velocity.dCartesian.dy += ddy * dt;
|
||||||
velocity.dTheta += (sumOfForces.torque * dt) / inertia;
|
velocity.dTheta += (sumOfForces.torque * dt) / inertia;
|
||||||
|
|
||||||
// clear the forces
|
// clear the forces
|
||||||
entity.getComponent<Forces>(ComponentNames.Forces).forces = [];
|
entity.getComponent<Forces>(ComponentNames.Forces).forces = [];
|
||||||
|
|
||||||
@ -71,11 +72,17 @@ export class Physics extends System {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
game.componentEntities.get(ComponentNames.Velocity)?.forEach((entityId) => {
|
game.forEachEntityWithComponent(ComponentNames.Velocity, (entity) => {
|
||||||
const entity = game.entities.get(entityId);
|
const velocity: Velocity = new Velocity();
|
||||||
const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
|
const control = entity.getComponent<Control>(ComponentNames.Control);
|
||||||
|
|
||||||
|
velocity.add(entity.getComponent<Velocity>(ComponentNames.Velocity));
|
||||||
|
if (control) {
|
||||||
|
velocity.add(control.controlVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox
|
ComponentNames.BoundingBox,
|
||||||
);
|
);
|
||||||
|
|
||||||
// integrate velocity
|
// integrate velocity
|
||||||
@ -86,6 +93,11 @@ export class Physics extends System {
|
|||||||
(boundingBox.rotation < 0
|
(boundingBox.rotation < 0
|
||||||
? 360 + boundingBox.rotation
|
? 360 + boundingBox.rotation
|
||||||
: boundingBox.rotation) % 360;
|
: boundingBox.rotation) % 360;
|
||||||
|
|
||||||
|
// clear the control velocity
|
||||||
|
if (control) {
|
||||||
|
control.controlVelocity = new Velocity();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { System, SystemNames } from ".";
|
import { System, SystemNames } from ".";
|
||||||
import { BoundingBox, ComponentNames, Sprite } from "../components";
|
import { BoundingBox, ComponentNames, Sprite } from "../components";
|
||||||
import type { Entity } from "../entities";
|
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
import type { DrawArgs } from "../interfaces";
|
|
||||||
import { clamp } from "../utils";
|
import { clamp } from "../utils";
|
||||||
|
|
||||||
export class Render extends System {
|
export class Render extends System {
|
||||||
@ -16,15 +14,12 @@ export class Render extends System {
|
|||||||
public update(dt: number, game: Game) {
|
public update(dt: number, game: Game) {
|
||||||
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
||||||
|
|
||||||
game.componentEntities.get(ComponentNames.Sprite)?.forEach((entityId) => {
|
game.forEachEntityWithComponent(ComponentNames.Sprite, (entity) => {
|
||||||
const entity = game.entities.get(entityId);
|
|
||||||
const sprite = entity.getComponent<Sprite>(ComponentNames.Sprite);
|
const sprite = entity.getComponent<Sprite>(ComponentNames.Sprite);
|
||||||
sprite.update(dt);
|
sprite.update(dt);
|
||||||
|
|
||||||
let drawArgs: DrawArgs;
|
|
||||||
if (entity.hasComponent(ComponentNames.BoundingBox)) {
|
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox
|
ComponentNames.BoundingBox,
|
||||||
);
|
);
|
||||||
|
|
||||||
// don't render if we're outside the screen
|
// don't render if we're outside the screen
|
||||||
@ -32,23 +27,23 @@ export class Render extends System {
|
|||||||
clamp(
|
clamp(
|
||||||
boundingBox.center.y,
|
boundingBox.center.y,
|
||||||
-boundingBox.dimension.height / 2,
|
-boundingBox.dimension.height / 2,
|
||||||
this.ctx.canvas.height + boundingBox.dimension.height / 2
|
this.ctx.canvas.height + boundingBox.dimension.height / 2,
|
||||||
) != boundingBox.center.y ||
|
) != boundingBox.center.y ||
|
||||||
clamp(
|
clamp(
|
||||||
boundingBox.center.x,
|
boundingBox.center.x,
|
||||||
-boundingBox.dimension.width / 2,
|
-boundingBox.dimension.width / 2,
|
||||||
this.ctx.canvas.width + boundingBox.dimension.width / 2
|
this.ctx.canvas.width + boundingBox.dimension.width / 2,
|
||||||
) != boundingBox.center.x
|
) != boundingBox.center.x
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawArgs = {
|
const drawArgs = {
|
||||||
center: boundingBox.center,
|
center: boundingBox.center,
|
||||||
dimension: boundingBox.dimension,
|
dimension: boundingBox.dimension,
|
||||||
rotation: boundingBox.rotation,
|
rotation: boundingBox.rotation,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
sprite.draw(this.ctx, drawArgs);
|
sprite.draw(this.ctx, drawArgs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Entity } from "../entities";
|
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
|
|
||||||
export abstract class System {
|
export abstract class System {
|
||||||
|
@ -14,22 +14,15 @@ export class WallBounds extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public update(_dt: number, game: Game) {
|
public update(_dt: number, game: Game) {
|
||||||
game.componentEntities
|
game.forEachEntityWithComponent(ComponentNames.WallBounded, (entity) => {
|
||||||
.get(ComponentNames.WallBounded)
|
|
||||||
?.forEach((entityId) => {
|
|
||||||
const entity = game.entities.get(entityId);
|
|
||||||
if (!entity.hasComponent(ComponentNames.BoundingBox)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox
|
ComponentNames.BoundingBox,
|
||||||
);
|
);
|
||||||
|
|
||||||
boundingBox.center.x = clamp(
|
boundingBox.center.x = clamp(
|
||||||
boundingBox.center.x,
|
boundingBox.center.x,
|
||||||
boundingBox.dimension.width / 2,
|
boundingBox.dimension.width / 2,
|
||||||
this.screenWidth - boundingBox.dimension.width / 2
|
this.screenWidth - boundingBox.dimension.width / 2,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from "./rotateVector";
|
export * from "./rotateVector";
|
||||||
export * from "./normalizeVector";
|
|
||||||
export * from "./dotProduct";
|
export * from "./dotProduct";
|
||||||
export * from "./clamp";
|
export * from "./clamp";
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import type { Coord2D } from "../interfaces";
|
|
||||||
|
|
||||||
export const normalizeVector = (vector: Coord2D): Coord2D => {
|
|
||||||
const { x, y } = vector;
|
|
||||||
const length = Math.sqrt(x * x + y * y);
|
|
||||||
|
|
||||||
return { x: x / length, y: y / length };
|
|
||||||
};
|
|
@ -1,21 +1,17 @@
|
|||||||
import { Game } from "../../engine/Game";
|
import { Game } from "../../engine/Game";
|
||||||
import { Floor, Player } from "../../engine/entities";
|
import { Floor, Player } from "../../engine/entities";
|
||||||
import {
|
import { WallBounds, Physics, Collision } from "../../engine/systems";
|
||||||
WallBounds,
|
|
||||||
FacingDirection,
|
|
||||||
Physics,
|
|
||||||
Input,
|
|
||||||
Collision,
|
|
||||||
} from "../../engine/systems";
|
|
||||||
import { Miscellaneous } from "../../engine/config";
|
import { Miscellaneous } from "../../engine/config";
|
||||||
|
|
||||||
const TICK_RATE = 60 / 1000;
|
const TICK_RATE = 60 / 1000;
|
||||||
|
|
||||||
const game = new Game();
|
const game = new Game();
|
||||||
|
|
||||||
[new Physics(), new Collision(), new WallBounds(Miscellaneous.WIDTH)].forEach(
|
[
|
||||||
(system) => game.addSystem(system)
|
new Physics(),
|
||||||
);
|
new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }),
|
||||||
|
new WallBounds(Miscellaneous.WIDTH),
|
||||||
|
].forEach((system) => game.addSystem(system));
|
||||||
|
|
||||||
[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity));
|
[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user