summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/Game.ts22
-rw-r--r--engine/components/BoundingBox.ts61
-rw-r--r--engine/components/Collide.ts2
-rw-r--r--engine/components/Control.ts15
-rw-r--r--engine/components/FacingDirection.ts2
-rw-r--r--engine/components/Forces.ts6
-rw-r--r--engine/components/Gravity.ts4
-rw-r--r--engine/components/Jump.ts2
-rw-r--r--engine/components/Mass.ts2
-rw-r--r--engine/components/Moment.ts2
-rw-r--r--engine/components/NetworkUpdateable.ts7
-rw-r--r--engine/components/Sprite.ts20
-rw-r--r--engine/components/TopCollidable.ts2
-rw-r--r--engine/components/Velocity.ts24
-rw-r--r--engine/components/WallBounded.ts2
-rw-r--r--engine/components/index.ts31
-rw-r--r--engine/components/names.ts27
-rw-r--r--engine/config/assets.ts16
-rw-r--r--engine/config/constants.ts26
-rw-r--r--engine/config/index.ts6
-rw-r--r--engine/config/sprites.ts16
-rw-r--r--engine/entities/Entity.ts46
-rw-r--r--engine/entities/Floor.ts41
-rw-r--r--engine/entities/Player.ts67
-rw-r--r--engine/entities/index.ts7
-rw-r--r--engine/entities/names.ts4
-rw-r--r--engine/interfaces/Action.ts2
-rw-r--r--engine/interfaces/Direction.ts8
-rw-r--r--engine/interfaces/Draw.ts2
-rw-r--r--engine/interfaces/Vec2.ts7
-rw-r--r--engine/interfaces/index.ts8
-rw-r--r--engine/network/index.ts37
-rw-r--r--engine/structures/Grid.ts104
-rw-r--r--engine/structures/QuadTree.ts97
-rw-r--r--engine/structures/RefreshingCollisionFinderBehavior.ts14
-rw-r--r--engine/structures/index.ts4
-rw-r--r--engine/systems/Collision.ts149
-rw-r--r--engine/systems/FacingDirection.ts27
-rw-r--r--engine/systems/Input.ts149
-rw-r--r--engine/systems/NetworkUpdate.ts72
-rw-r--r--engine/systems/Physics.ts50
-rw-r--r--engine/systems/Render.ts16
-rw-r--r--engine/systems/System.ts2
-rw-r--r--engine/systems/WallBounds.ts20
-rw-r--r--engine/systems/index.ts17
-rw-r--r--engine/systems/names.ts13
-rw-r--r--engine/utils/coding.ts27
-rw-r--r--engine/utils/dotProduct.ts2
-rw-r--r--engine/utils/index.ts7
-rw-r--r--engine/utils/rotateVector.ts4
50 files changed, 893 insertions, 405 deletions
diff --git a/engine/Game.ts b/engine/Game.ts
index 07d06e8..cdd3507 100644
--- a/engine/Game.ts
+++ b/engine/Game.ts
@@ -1,5 +1,5 @@
-import { Entity } from "./entities";
-import { System } from "./systems";
+import { Entity } from './entities';
+import { System } from './systems';
export class Game {
private systemOrder: string[];
@@ -7,9 +7,9 @@ export class Game {
private running: boolean;
private lastTimeStamp: number;
- public entities: Map<number, Entity>;
+ public entities: Map<string, Entity>;
public systems: Map<string, System>;
- public componentEntities: Map<string, Set<number>>;
+ public componentEntities: Map<string, Set<string>>;
constructor() {
this.lastTimeStamp = performance.now();
@@ -29,17 +29,17 @@ export class Game {
this.entities.set(entity.id, entity);
}
- public getEntity(id: number): Entity | undefined {
+ public getEntity(id: string): Entity | undefined {
return this.entities.get(id);
}
- public removeEntity(id: number) {
+ public removeEntity(id: string) {
this.entities.delete(id);
}
public forEachEntityWithComponent(
componentName: string,
- callback: (entity: Entity) => void,
+ callback: (entity: Entity) => void
) {
this.componentEntities.get(componentName)?.forEach((entityId) => {
const entity = this.getEntity(entityId);
@@ -60,7 +60,7 @@ export class Game {
return this.systems.get(name);
}
- public doGameLoop = (timeStamp: number) => {
+ public doGameLoop(timeStamp: number) {
if (!this.running) {
return;
}
@@ -75,16 +75,16 @@ export class Game {
if (!this.componentEntities.has(component.name)) {
this.componentEntities.set(
component.name,
- new Set<number>([entity.id]),
+ new Set<string>([entity.id])
);
return;
}
this.componentEntities.get(component.name)?.add(entity.id);
- }),
+ })
);
this.systemOrder.forEach((systemName) => {
this.systems.get(systemName)?.update(dt, this);
});
- };
+ }
}
diff --git a/engine/components/BoundingBox.ts b/engine/components/BoundingBox.ts
index 5e21b2f..921feb9 100644
--- a/engine/components/BoundingBox.ts
+++ b/engine/components/BoundingBox.ts
@@ -1,6 +1,6 @@
-import { Component, ComponentNames } from ".";
-import type { Coord2D, Dimension2D } from "../interfaces";
-import { dotProduct, rotateVector } from "../utils";
+import { Component, ComponentNames } from '.';
+import type { Coord2D, Dimension2D } from '../interfaces';
+import { dotProduct, rotateVector } from '../utils';
export class BoundingBox extends Component {
public center: Coord2D;
@@ -15,8 +15,27 @@ export class BoundingBox extends Component {
this.rotation = rotation ?? 0;
}
- // https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
public isCollidingWith(box: BoundingBox): boolean {
+ if (this.rotation == 0 && box.rotation == 0) {
+ const thisTopLeft = this.getTopLeft();
+ const thisBottomRight = this.getBottomRight();
+
+ const thatTopLeft = box.getTopLeft();
+ const thatBottomRight = box.getBottomRight();
+
+ if (
+ thisBottomRight.x <= thatTopLeft.x ||
+ thisTopLeft.x >= thatBottomRight.x ||
+ thisBottomRight.y <= thatTopLeft.y ||
+ thisTopLeft.y >= thatBottomRight.y
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
const boxes = [this.getVertices(), box.getVertices()];
for (const poly of boxes) {
for (let i = 0; i < poly.length; i++) {
@@ -29,8 +48,8 @@ export class BoundingBox extends Component {
const projection = dotProduct(normal, vertex);
return [Math.min(min, projection), Math.max(max, projection)];
},
- [Infinity, -Infinity],
- ),
+ [Infinity, -Infinity]
+ )
);
if (maxThis < minBox || maxBox < minThis) return false;
@@ -45,20 +64,22 @@ export class BoundingBox extends Component {
{ x: -this.dimension.width / 2, y: -this.dimension.height / 2 },
{ x: -this.dimension.width / 2, y: this.dimension.height / 2 },
{ x: this.dimension.width / 2, y: this.dimension.height / 2 },
- { x: this.dimension.width / 2, y: -this.dimension.height / 2 },
+ { x: this.dimension.width / 2, y: -this.dimension.height / 2 }
]
- .map((vertex) => rotateVector(vertex, this.rotation))
+ .map((vertex) => rotateVector(vertex, this.rotation)) // rotate
.map((vertex) => {
+ // translate
return {
x: vertex.x + this.center.x,
- y: vertex.y + this.center.y,
+ y: vertex.y + this.center.y
};
});
}
- public getRotationInPiOfUnitCircle() {
+ public getRotationInPiOfUnitCircle(): number {
let rads = this.rotation * (Math.PI / 180);
if (rads >= Math.PI) {
+ // Physics system guarantees rotation \in [0, 360)
rads -= Math.PI;
}
return rads;
@@ -68,17 +89,33 @@ export class BoundingBox extends Component {
let rads = this.getRotationInPiOfUnitCircle();
const { width, height } = this.dimension;
+ if (rads == 0) return this.dimension;
+
if (rads <= Math.PI / 2) {
return {
width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)),
- height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads)),
+ height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads))
};
}
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)),
+ height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads))
+ };
+ }
+
+ public getTopLeft(): Coord2D {
+ return {
+ x: this.center.x - this.dimension.width / 2,
+ y: this.center.y - this.dimension.height / 2
+ };
+ }
+
+ public getBottomRight(): Coord2D {
+ return {
+ x: this.center.x + this.dimension.width / 2,
+ y: this.center.y + this.dimension.height / 2
};
}
}
diff --git a/engine/components/Collide.ts b/engine/components/Collide.ts
index 889ecf8..ed72b92 100644
--- a/engine/components/Collide.ts
+++ b/engine/components/Collide.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class Collide extends Component {
constructor() {
diff --git a/engine/components/Control.ts b/engine/components/Control.ts
index 1e782ee..d3987d7 100644
--- a/engine/components/Control.ts
+++ b/engine/components/Control.ts
@@ -1,11 +1,18 @@
-import { Component, ComponentNames, Velocity } from ".";
+import { Component, ComponentNames, Velocity } from '.';
export class Control extends Component {
- public controlVelocity: Velocity;
+ public controlVelocityComponent: Velocity;
+ public controllableBy: string;
+ public isControllable: boolean; // computed each update in the input system
- constructor(controlVelocity: Velocity = new Velocity()) {
+ constructor(
+ controllableBy: string,
+ controlVelocityComponent: Velocity = new Velocity()
+ ) {
super(ComponentNames.Control);
- this.controlVelocity = controlVelocity;
+ this.controllableBy = controllableBy;
+ this.controlVelocityComponent = controlVelocityComponent;
+ this.isControllable = false;
}
}
diff --git a/engine/components/FacingDirection.ts b/engine/components/FacingDirection.ts
index 1c701a3..8c2a9d2 100644
--- a/engine/components/FacingDirection.ts
+++ b/engine/components/FacingDirection.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames, Sprite } from ".";
+import { Component, ComponentNames, Sprite } from '.';
export class FacingDirection extends Component {
public readonly facingLeftSprite: Sprite;
diff --git a/engine/components/Forces.ts b/engine/components/Forces.ts
index 91ae1c1..e397985 100644
--- a/engine/components/Forces.ts
+++ b/engine/components/Forces.ts
@@ -1,6 +1,6 @@
-import type { Force2D } from "../interfaces";
-import { Component } from "./Component";
-import { ComponentNames } from ".";
+import type { Force2D } from '../interfaces';
+import { Component } from './Component';
+import { ComponentNames } from '.';
/**
* A list of forces and torque, (in newtons, and newton-meters respectively)
diff --git a/engine/components/Gravity.ts b/engine/components/Gravity.ts
index 89fcb67..dd6dd2e 100644
--- a/engine/components/Gravity.ts
+++ b/engine/components/Gravity.ts
@@ -1,7 +1,7 @@
-import { ComponentNames, Component } from ".";
+import { ComponentNames, Component } from '.';
export class Gravity extends Component {
- private static DEFAULT_TERMINAL_VELOCITY = 5;
+ private static DEFAULT_TERMINAL_VELOCITY = 4.5;
public terminalVelocity: number;
diff --git a/engine/components/Jump.ts b/engine/components/Jump.ts
index 0b40767..6cbfb08 100644
--- a/engine/components/Jump.ts
+++ b/engine/components/Jump.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class Jump extends Component {
public canJump: boolean;
diff --git a/engine/components/Mass.ts b/engine/components/Mass.ts
index daa2d71..a7f98fd 100644
--- a/engine/components/Mass.ts
+++ b/engine/components/Mass.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class Mass extends Component {
public mass: number;
diff --git a/engine/components/Moment.ts b/engine/components/Moment.ts
index 3d0dd2f..cd76294 100644
--- a/engine/components/Moment.ts
+++ b/engine/components/Moment.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class Moment extends Component {
public inertia: number;
diff --git a/engine/components/NetworkUpdateable.ts b/engine/components/NetworkUpdateable.ts
new file mode 100644
index 0000000..014270c
--- /dev/null
+++ b/engine/components/NetworkUpdateable.ts
@@ -0,0 +1,7 @@
+import { Component, ComponentNames } from '.';
+
+export class NetworkUpdateable extends Component {
+ constructor() {
+ super(ComponentNames.NetworkUpdateable);
+ }
+}
diff --git a/engine/components/Sprite.ts b/engine/components/Sprite.ts
index bdb4982..36b944e 100644
--- a/engine/components/Sprite.ts
+++ b/engine/components/Sprite.ts
@@ -1,5 +1,5 @@
-import { Component, ComponentNames } from ".";
-import type { Dimension2D, DrawArgs, Coord2D } from "../interfaces";
+import { Component, ComponentNames } from '.';
+import type { Dimension2D, DrawArgs, Coord2D } from '../interfaces';
export class Sprite extends Component {
private sheet: HTMLImageElement;
@@ -17,7 +17,7 @@ export class Sprite extends Component {
spriteImgPos: Coord2D,
spriteImgDimensions: Dimension2D,
msPerFrame: number,
- numFrames: number,
+ numFrames: number
) {
super(ComponentNames.Sprite);
@@ -56,12 +56,12 @@ export class Sprite extends Component {
ctx.drawImage(
this.sheet,
...this.getSpriteArgs(),
- ...this.getDrawArgs(drawArgs),
+ ...this.getDrawArgs(drawArgs)
);
if (tint) {
ctx.globalAlpha = 0.5;
- ctx.globalCompositeOperation = "source-atop";
+ ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = tint;
ctx.fillRect(...this.getDrawArgs(drawArgs));
}
@@ -74,19 +74,23 @@ export class Sprite extends Component {
this.spriteImgPos.x + this.currentFrame * this.spriteImgDimensions.width,
this.spriteImgPos.y,
this.spriteImgDimensions.width,
- this.spriteImgDimensions.height,
+ this.spriteImgDimensions.height
];
}
private getDrawArgs({
center,
- dimension,
+ dimension
}: DrawArgs): [dx: number, dy: number, dw: number, dh: number] {
return [
center.x - dimension.width / 2,
center.y - dimension.height / 2,
dimension.width,
- dimension.height,
+ dimension.height
];
}
+
+ public getSpriteDimensions() {
+ return this.spriteImgDimensions;
+ }
}
diff --git a/engine/components/TopCollidable.ts b/engine/components/TopCollidable.ts
index 7fb147d..05ce484 100644
--- a/engine/components/TopCollidable.ts
+++ b/engine/components/TopCollidable.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class TopCollidable extends Component {
constructor() {
diff --git a/engine/components/Velocity.ts b/engine/components/Velocity.ts
index 068d8cd..0071891 100644
--- a/engine/components/Velocity.ts
+++ b/engine/components/Velocity.ts
@@ -1,23 +1,23 @@
-import type { Velocity2D } from "../interfaces";
-import { Component } from "./Component";
-import { ComponentNames } from ".";
+import type { Velocity2D } from '../interfaces';
+import { Component } from './Component';
+import { ComponentNames } from '.';
export class Velocity extends Component {
- public dCartesian: Velocity2D;
- public dTheta: number;
+ public velocity: Velocity2D;
- constructor(dCartesian: Velocity2D = { dx: 0, dy: 0 }, dTheta: number = 0) {
+ constructor(
+ velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 }
+ ) {
super(ComponentNames.Velocity);
- this.dCartesian = dCartesian;
- this.dTheta = dTheta;
+ this.velocity = velocity;
}
- public add(velocity?: Velocity) {
+ public add(velocity?: Velocity2D) {
if (velocity) {
- this.dCartesian.dx += velocity.dCartesian.dx;
- this.dCartesian.dy += velocity.dCartesian.dy;
- this.dTheta += velocity.dTheta;
+ this.velocity.dCartesian.dx += velocity.dCartesian.dx;
+ this.velocity.dCartesian.dy += velocity.dCartesian.dy;
+ this.velocity.dTheta += velocity.dTheta;
}
}
}
diff --git a/engine/components/WallBounded.ts b/engine/components/WallBounded.ts
index 5f787e1..c1745a8 100644
--- a/engine/components/WallBounded.ts
+++ b/engine/components/WallBounded.ts
@@ -1,4 +1,4 @@
-import { Component, ComponentNames } from ".";
+import { Component, ComponentNames } from '.';
export class WallBounded extends Component {
constructor() {
diff --git a/engine/components/index.ts b/engine/components/index.ts
index 67f1259..6d7c1e5 100644
--- a/engine/components/index.ts
+++ b/engine/components/index.ts
@@ -1,15 +1,16 @@
-export * from "./Component";
-export * from "./BoundingBox";
-export * from "./Velocity";
-export * from "./Forces";
-export * from "./Sprite";
-export * from "./FacingDirection";
-export * from "./Jump";
-export * from "./TopCollidable";
-export * from "./Collide";
-export * from "./Control";
-export * from "./WallBounded";
-export * from "./Gravity";
-export * from "./Mass";
-export * from "./Moment";
-export * from "./names";
+export * from './Component';
+export * from './BoundingBox';
+export * from './Velocity';
+export * from './Forces';
+export * from './Sprite';
+export * from './FacingDirection';
+export * from './Jump';
+export * from './TopCollidable';
+export * from './Collide';
+export * from './Control';
+export * from './WallBounded';
+export * from './Gravity';
+export * from './Mass';
+export * from './Moment';
+export * from './NetworkUpdateable';
+export * from './names';
diff --git a/engine/components/names.ts b/engine/components/names.ts
index e2ee3d3..97b4edd 100644
--- a/engine/components/names.ts
+++ b/engine/components/names.ts
@@ -1,15 +1,16 @@
export namespace ComponentNames {
- export const Sprite = "Sprite";
- export const BoundingBox = "BoundingBox";
- export const Velocity = "Velocity";
- export const FacingDirection = "FacingDirection";
- export const Control = "Control";
- export const Jump = "Jump";
- export const TopCollidable = "TopCollidable";
- export const Collide = "Collide";
- export const WallBounded = "WallBounded";
- export const Gravity = "Gravity";
- export const Forces = "Forces";
- export const Mass = "Mass";
- export const Moment = "Moment";
+ export const Sprite = 'Sprite';
+ export const BoundingBox = 'BoundingBox';
+ export const Velocity = 'Velocity';
+ export const FacingDirection = 'FacingDirection';
+ export const Control = 'Control';
+ export const Jump = 'Jump';
+ export const TopCollidable = 'TopCollidable';
+ export const Collide = 'Collide';
+ export const WallBounded = 'WallBounded';
+ export const Gravity = 'Gravity';
+ export const Forces = 'Forces';
+ export const Mass = 'Mass';
+ export const Moment = 'Moment';
+ export const NetworkUpdateable = 'NetworkUpdateable';
}
diff --git a/engine/config/assets.ts b/engine/config/assets.ts
index 173bab3..289f181 100644
--- a/engine/config/assets.ts
+++ b/engine/config/assets.ts
@@ -1,10 +1,10 @@
-import type { SpriteSpec } from "./sprites";
-import { SPRITE_SPECS } from "./sprites";
+import type { SpriteSpec } from './sprites';
+import { SPRITE_SPECS } from './sprites';
export const IMAGES = new Map<string, HTMLImageElement>();
export const loadSpritesIntoImageElements = (
- spriteSpecs: Partial<SpriteSpec>[],
+ spriteSpecs: Partial<SpriteSpec>[]
): Promise<void>[] => {
const spritePromises: Promise<void>[] = [];
@@ -17,13 +17,13 @@ export const loadSpritesIntoImageElements = (
spritePromises.push(
new Promise((resolve) => {
img.onload = () => resolve();
- }),
+ })
);
}
if (spriteSpec.states) {
spritePromises.push(
- ...loadSpritesIntoImageElements(Array.from(spriteSpec.states.values())),
+ ...loadSpritesIntoImageElements(Array.from(spriteSpec.states.values()))
);
}
}
@@ -35,8 +35,8 @@ export const loadAssets = () =>
Promise.all([
...loadSpritesIntoImageElements(
Array.from(SPRITE_SPECS.keys()).map(
- (key) => SPRITE_SPECS.get(key) as SpriteSpec,
- ),
- ),
+ (key) => SPRITE_SPECS.get(key) as SpriteSpec
+ )
+ )
// TODO: Sound
]);
diff --git a/engine/config/constants.ts b/engine/config/constants.ts
index 3d536d3..dc98ad0 100644
--- a/engine/config/constants.ts
+++ b/engine/config/constants.ts
@@ -1,34 +1,39 @@
-import { Action } from "../interfaces";
+import { Action } from '../interfaces';
export namespace KeyConstants {
export const KeyActions: Record<string, Action> = {
a: Action.MOVE_LEFT,
- ArrowLeft: Action.MOVE_LEFT,
+ arrowleft: Action.MOVE_LEFT,
+
d: Action.MOVE_RIGHT,
- ArrowRight: Action.MOVE_RIGHT,
+ arrowright: Action.MOVE_RIGHT,
+
w: Action.JUMP,
- ArrowUp: Action.JUMP,
+ arrowup: Action.JUMP,
+
+ ' ': Action.JUMP
};
+ // value -> [key] from KeyActions
export const ActionKeys: Map<Action, string[]> = Object.keys(
- KeyActions,
+ KeyActions
).reduce((acc: Map<Action, string[]>, key) => {
- const action = KeyActions[key];
+ const action = KeyActions[key.toLowerCase()];
if (acc.has(action)) {
- acc.get(action)?.push(key);
+ acc.get(action)!.push(key);
return acc;
}
acc.set(action, [key]);
return acc;
- }, new Map<Action, string[]>());
+ }, new Map());
}
export namespace PhysicsConstants {
export const MAX_JUMP_TIME_MS = 150;
export const GRAVITY = 0.0075;
- export const PLAYER_MOVE_VEL = 1;
+ export const PLAYER_MOVE_VEL = 0.8;
export const PLAYER_JUMP_ACC = -0.008;
export const PLAYER_JUMP_INITIAL_VEL = -1;
}
@@ -36,4 +41,7 @@ export namespace PhysicsConstants {
export namespace Miscellaneous {
export const WIDTH = 600;
export const HEIGHT = 800;
+
+ export const DEFAULT_GRID_WIDTH = 30;
+ export const DEFAULT_GRID_HEIGHT = 30;
}
diff --git a/engine/config/index.ts b/engine/config/index.ts
index 7a1052a..03b2246 100644
--- a/engine/config/index.ts
+++ b/engine/config/index.ts
@@ -1,3 +1,3 @@
-export * from "./constants";
-export * from "./assets.ts";
-export * from "./sprites.ts";
+export * from './constants';
+export * from './assets.ts';
+export * from './sprites.ts';
diff --git a/engine/config/sprites.ts b/engine/config/sprites.ts
index 1f65c18..e5fcd31 100644
--- a/engine/config/sprites.ts
+++ b/engine/config/sprites.ts
@@ -1,7 +1,7 @@
export enum Sprites {
FLOOR,
TRAMPOLINE,
- COFFEE,
+ COFFEE
}
export interface SpriteSpec {
@@ -22,12 +22,12 @@ const floorSpriteSpec = {
height: 40,
frames: 3,
msPerFrame: 125,
- states: new Map<number, Partial<SpriteSpec>>(),
+ states: new Map<number, Partial<SpriteSpec>>()
};
[40, 80, 120, 160].forEach((width) => {
floorSpriteSpec.states.set(width, {
width,
- sheet: `/assets/floor_tile_${width}.png`,
+ sheet: `/assets/floor_tile_${width}.png`
});
});
SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec);
@@ -37,12 +37,12 @@ const coffeeSpriteSpec = {
width: 60,
height: 45,
frames: 3,
- states: new Map<string, Partial<SpriteSpec>>(),
+ states: new Map<string, Partial<SpriteSpec>>()
};
-coffeeSpriteSpec.states.set("LEFT", {
- sheet: "/assets/coffee_left.png",
+coffeeSpriteSpec.states.set('LEFT', {
+ sheet: '/assets/coffee_left.png'
});
-coffeeSpriteSpec.states.set("RIGHT", {
- sheet: "/assets/coffee_right.png",
+coffeeSpriteSpec.states.set('RIGHT', {
+ sheet: '/assets/coffee_right.png'
});
SPRITE_SPECS.set(Sprites.COFFEE, coffeeSpriteSpec);
diff --git a/engine/entities/Entity.ts b/engine/entities/Entity.ts
index ca8d314..63fb370 100644
--- a/engine/entities/Entity.ts
+++ b/engine/entities/Entity.ts
@@ -1,13 +1,17 @@
-import type { Component } from "../components";
+import { EntityNames, Floor, Player } from '.';
+import { type Component } from '../components';
-export abstract class Entity {
- private static ID = 0;
+const randomId = () =>
+ (performance.now() + Math.random() * 10_000_000).toString();
- public readonly id: number;
- public readonly components: Map<string, Component>;
+export abstract class Entity {
+ public id: string;
+ public components: Map<string, Component>;
+ public name: string;
- constructor() {
- this.id = Entity.ID++;
+ constructor(name: string, id: string = randomId()) {
+ this.name = name;
+ this.id = id;
this.components = new Map();
}
@@ -17,7 +21,7 @@ export abstract class Entity {
public getComponent<T extends Component>(name: string): T {
if (!this.hasComponent(name)) {
- throw new Error("Entity does not have component " + name);
+ throw new Error('Entity does not have component ' + name);
}
return this.components.get(name) as T;
}
@@ -29,4 +33,30 @@ export abstract class Entity {
public hasComponent(name: string): boolean {
return this.components.has(name);
}
+
+ public static from(entityName: string, id: string, args: any): Entity {
+ let entity: Entity;
+
+ switch (entityName) {
+ case EntityNames.Player:
+ const player = new Player();
+ player.setFrom(args);
+ entity = player;
+ break;
+ case EntityNames.Floor:
+ const floor = new Floor(args.floorWidth);
+ floor.setFrom(args);
+ entity = floor;
+ break;
+ default:
+ throw new Error('.from() Entity type not implemented: ' + entityName);
+ }
+
+ entity.id = id;
+ return entity;
+ }
+
+ public abstract setFrom(args: Record<string, any>): void;
+
+ public abstract serialize(): Record<string, any>;
}
diff --git a/engine/entities/Floor.ts b/engine/entities/Floor.ts
index 44587e6..b4f48e5 100644
--- a/engine/entities/Floor.ts
+++ b/engine/entities/Floor.ts
@@ -1,15 +1,19 @@
-import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config";
-import { BoundingBox, Sprite } from "../components";
-import { TopCollidable } from "../components/TopCollidable";
-import { Entity } from "../entities";
+import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from '../config';
+import { BoundingBox, ComponentNames, Sprite } from '../components';
+import { TopCollidable } from '../components/TopCollidable';
+import { Entity, EntityNames } from '../entities';
export class Floor extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
- Sprites.FLOOR,
+ Sprites.FLOOR
) as SpriteSpec;
+ private width: number;
+
constructor(width: number) {
- super();
+ super(EntityNames.Floor);
+
+ this.width = width;
this.addComponent(
new Sprite(
@@ -17,17 +21,28 @@ export class Floor extends Entity {
{ x: 0, y: 0 },
{ width, height: Floor.spriteSpec.height },
Floor.spriteSpec.msPerFrame,
- Floor.spriteSpec.frames,
- ),
+ Floor.spriteSpec.frames
+ )
);
+ this.addComponent(new TopCollidable());
+ }
+
+ public serialize() {
+ return {
+ floorWidth: this.width,
+ boundingBox: this.getComponent<BoundingBox>(ComponentNames.BoundingBox)
+ };
+ }
+
+ public setFrom(args: any) {
+ const { boundingBox } = args;
this.addComponent(
new BoundingBox(
- { x: 300, y: 300 },
- { width, height: Floor.spriteSpec.height },
- ),
+ boundingBox.center,
+ boundingBox.dimension,
+ boundingBox.rotation
+ )
);
-
- this.addComponent(new TopCollidable());
}
}
diff --git a/engine/entities/Player.ts b/engine/entities/Player.ts
index 45d7500..4d91c6f 100644
--- a/engine/entities/Player.ts
+++ b/engine/entities/Player.ts
@@ -1,5 +1,5 @@
-import { Entity } from ".";
-import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config";
+import { Entity, EntityNames } from '.';
+import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from '../config';
import {
Jump,
FacingDirection,
@@ -10,32 +10,38 @@ import {
WallBounded,
Forces,
Collide,
- Control,
Mass,
Moment,
-} from "../components";
-import { Direction } from "../interfaces";
+ ComponentNames,
+ Control
+} from '../components';
+import { Direction } from '../interfaces';
export class Player extends Entity {
private static MASS: number = 10;
- private static MOI: number = 1000;
+ private static MOI: number = 100;
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
- Sprites.COFFEE,
+ Sprites.COFFEE
) as SpriteSpec;
constructor() {
- super();
+ super(EntityNames.Player);
this.addComponent(
new BoundingBox(
- { x: 300, y: 100 },
+ {
+ x: 0,
+ y: 0
+ },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
- 0,
- ),
+ 0
+ )
);
- this.addComponent(new Velocity({ dx: 0, dy: 0 }, 0));
+ this.addComponent(
+ new Velocity({ dCartesian: { dx: 0, dy: 0 }, dTheta: 0 })
+ );
this.addComponent(new Mass(Player.MASS));
this.addComponent(new Moment(Player.MOI));
@@ -43,7 +49,6 @@ export class Player extends Entity {
this.addComponent(new Gravity());
this.addComponent(new Jump());
- this.addComponent(new Control());
this.addComponent(new Collide());
this.addComponent(new WallBounded());
@@ -59,11 +64,41 @@ export class Player extends Entity {
{ x: 0, y: 0 },
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
Player.spriteSpec.msPerFrame,
- Player.spriteSpec.frames,
- ),
+ Player.spriteSpec.frames
+ )
);
this.addComponent(new FacingDirection(leftSprite, rightSprite));
- this.addComponent(leftSprite); // face Left by default
+ this.addComponent(leftSprite); // face left by default
+ }
+
+ public serialize(): Record<string, any> {
+ return {
+ control: this.getComponent<Control>(ComponentNames.Control),
+ boundingBox: this.getComponent<BoundingBox>(ComponentNames.BoundingBox),
+ velocity: this.getComponent<Velocity>(ComponentNames.Velocity),
+ forces: this.getComponent<Forces>(ComponentNames.Forces)
+ };
+ }
+
+ public setFrom(args: Record<string, any>) {
+ const { control, velocity, forces, boundingBox } = args;
+
+ let center = boundingBox.center;
+
+ const myCenter = this.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ ).center;
+ const distance = Math.sqrt(
+ Math.pow(center.y - myCenter.y, 2) + Math.pow(center.x - myCenter.x, 2)
+ );
+ if (distance < 30) center = myCenter;
+
+ [
+ Object.assign(new Control(control.controllableBy), control),
+ new Velocity(velocity.velocity),
+ new Forces(forces.forces),
+ new BoundingBox(center, boundingBox.dimension, boundingBox.rotation)
+ ].forEach((component) => this.addComponent(component));
}
}
diff --git a/engine/entities/index.ts b/engine/entities/index.ts
index a921512..8aee83c 100644
--- a/engine/entities/index.ts
+++ b/engine/entities/index.ts
@@ -1,3 +1,4 @@
-export * from "./Entity";
-export * from "./Floor";
-export * from "./Player";
+export * from './Entity';
+export * from './Floor';
+export * from './Player';
+export * from './names';
diff --git a/engine/entities/names.ts b/engine/entities/names.ts
new file mode 100644
index 0000000..cf65f9f
--- /dev/null
+++ b/engine/entities/names.ts
@@ -0,0 +1,4 @@
+export namespace EntityNames {
+ export const Player = 'Player';
+ export const Floor = 'Floor';
+}
diff --git a/engine/interfaces/Action.ts b/engine/interfaces/Action.ts
index 61c89e1..f0e6a66 100644
--- a/engine/interfaces/Action.ts
+++ b/engine/interfaces/Action.ts
@@ -1,5 +1,5 @@
export enum Action {
MOVE_LEFT,
MOVE_RIGHT,
- JUMP,
+ JUMP
}
diff --git a/engine/interfaces/Direction.ts b/engine/interfaces/Direction.ts
index 0bc6ef3..af1c7ac 100644
--- a/engine/interfaces/Direction.ts
+++ b/engine/interfaces/Direction.ts
@@ -1,6 +1,6 @@
export enum Direction {
- UP = "UP",
- DOWN = "DOWN",
- LEFT = "LEFT",
- RIGHT = "RIGHT",
+ UP = 'UP',
+ DOWN = 'DOWN',
+ LEFT = 'LEFT',
+ RIGHT = 'RIGHT'
}
diff --git a/engine/interfaces/Draw.ts b/engine/interfaces/Draw.ts
index 6561a01..8479fe4 100644
--- a/engine/interfaces/Draw.ts
+++ b/engine/interfaces/Draw.ts
@@ -1,4 +1,4 @@
-import type { Coord2D, Dimension2D } from "./";
+import type { Coord2D, Dimension2D } from './';
export interface DrawArgs {
center: Coord2D;
diff --git a/engine/interfaces/Vec2.ts b/engine/interfaces/Vec2.ts
index b2bae37..04be4be 100644
--- a/engine/interfaces/Vec2.ts
+++ b/engine/interfaces/Vec2.ts
@@ -9,8 +9,11 @@ export interface Dimension2D {
}
export interface Velocity2D {
- dx: number;
- dy: number;
+ dCartesian: {
+ dx: number;
+ dy: number;
+ };
+ dTheta: number;
}
export interface Force2D {
diff --git a/engine/interfaces/index.ts b/engine/interfaces/index.ts
index 8cdf4d8..c2f6896 100644
--- a/engine/interfaces/index.ts
+++ b/engine/interfaces/index.ts
@@ -1,4 +1,4 @@
-export * from "./Vec2";
-export * from "./Draw";
-export * from "./Direction";
-export * from "./Action";
+export * from './Vec2';
+export * from './Draw';
+export * from './Direction';
+export * from './Action';
diff --git a/engine/network/index.ts b/engine/network/index.ts
new file mode 100644
index 0000000..5dc7ece
--- /dev/null
+++ b/engine/network/index.ts
@@ -0,0 +1,37 @@
+export enum MessageType {
+ NEW_ENTITIES = 'NEW_ENTITIES',
+ REMOVE_ENTITIES = 'REMOVE_ENTITIES',
+ UPDATE_ENTITIES = 'UPDATE_ENTITIES',
+ NEW_INPUT = 'NEW_INPUT',
+ REMOVE_INPUT = 'REMOVE_INPUT'
+}
+
+export type EntityAddBody = {
+ entityName: string;
+ id: string;
+ args: Record<string, any>;
+};
+
+export type EntityUpdateBody = {
+ id: string;
+ args: Record<string, any>;
+};
+
+export type Message = {
+ type: MessageType;
+ body: any;
+};
+
+export interface MessageQueueProvider {
+ getNewMessages(): Message[];
+ clearMessages(): void;
+}
+
+export interface MessagePublisher {
+ addMessage(message: Message): void;
+ publish(): void;
+}
+
+export interface MessageProcessor {
+ process(message: Message): void;
+}
diff --git a/engine/structures/Grid.ts b/engine/structures/Grid.ts
new file mode 100644
index 0000000..5f0e053
--- /dev/null
+++ b/engine/structures/Grid.ts
@@ -0,0 +1,104 @@
+import type { Coord2D, Dimension2D } from '../interfaces';
+import type { BoxedEntry, RefreshingCollisionFinderBehavior } from '.';
+import { Miscellaneous } from '../config/constants';
+
+export class Grid implements RefreshingCollisionFinderBehavior {
+ private cellEntities: Map<number, string[]>;
+
+ private gridDimension: Dimension2D;
+ private cellDimension: Dimension2D;
+ private topLeft: Coord2D;
+
+ constructor(
+ gridDimension: Dimension2D = {
+ width: Miscellaneous.WIDTH,
+ height: Miscellaneous.HEIGHT
+ },
+ cellDimension: Dimension2D = {
+ width: Miscellaneous.DEFAULT_GRID_WIDTH,
+ height: Miscellaneous.DEFAULT_GRID_HEIGHT
+ },
+ topLeft = { x: 0, y: 0 }
+ ) {
+ this.gridDimension = gridDimension;
+ this.cellDimension = cellDimension;
+ this.topLeft = topLeft;
+
+ this.cellEntities = new Map();
+ }
+
+ public insert(boxedEntry: BoxedEntry) {
+ this.getOverlappingCells(boxedEntry).forEach((gridIdx) => {
+ if (!this.cellEntities.has(gridIdx)) {
+ this.cellEntities.set(gridIdx, []);
+ }
+ this.cellEntities.get(gridIdx)!.push(boxedEntry.id);
+ });
+ }
+
+ public getNeighborIds(boxedEntry: BoxedEntry): Set<string> {
+ const neighborIds: Set<string> = new Set();
+ this.getOverlappingCells(boxedEntry).forEach((gridIdx) => {
+ if (this.cellEntities.has(gridIdx)) {
+ this.cellEntities.get(gridIdx)!.forEach((id) => neighborIds.add(id));
+ }
+ });
+ return neighborIds;
+ }
+
+ public clear() {
+ this.cellEntities.clear();
+ }
+
+ public setTopLeft(topLeft: Coord2D) {
+ this.topLeft = topLeft;
+ }
+
+ public setDimension(dimension: Dimension2D) {
+ this.gridDimension = dimension;
+ }
+
+ public setCellDimension(cellDimension: Dimension2D) {
+ this.cellDimension = cellDimension;
+ }
+
+ private getOverlappingCells(boxedEntry: BoxedEntry): number[] {
+ const { center, dimension } = boxedEntry;
+ const yBoxes = Math.ceil(
+ this.gridDimension.height / this.cellDimension.height
+ );
+ const xBoxes = Math.ceil(
+ this.gridDimension.width / this.cellDimension.width
+ );
+
+ const translated: Coord2D = {
+ y: center.y - this.topLeft.y,
+ x: center.x - this.topLeft.x
+ };
+
+ const topLeftBox = {
+ x: Math.floor(
+ (translated.x - dimension.width / 2) / this.cellDimension.width
+ ),
+ y: Math.floor(
+ (translated.y - dimension.height / 2) / this.cellDimension.height
+ )
+ };
+ const bottomRightBox = {
+ x: Math.floor(
+ (translated.x + dimension.width / 2) / this.cellDimension.width
+ ),
+ y: Math.floor(
+ (translated.y + dimension.height / 2) / this.cellDimension.height
+ )
+ };
+
+ const cells: number[] = [];
+
+ for (let y = topLeftBox.y; y <= bottomRightBox.y; ++y)
+ for (let x = topLeftBox.x; x <= bottomRightBox.x; ++x)
+ cells.push(yBoxes * y + x);
+
+ return cells;
+ }
+}
diff --git a/engine/structures/QuadTree.ts b/engine/structures/QuadTree.ts
index d1ff3b1..93702d0 100644
--- a/engine/structures/QuadTree.ts
+++ b/engine/structures/QuadTree.ts
@@ -1,19 +1,21 @@
-import type { Coord2D, Dimension2D } from "../interfaces";
-
-interface BoxedEntry {
- id: number;
- dimension: Dimension2D;
- center: Coord2D;
-}
+import type { Coord2D, Dimension2D } from '../interfaces';
+import type { BoxedEntry, RefreshingCollisionFinderBehavior } from '.';
enum Quadrant {
I,
II,
III,
- IV,
+ IV
}
-export class QuadTree {
+/*
+ unused due to performance problems. here anyways, in case it _really_ is necessary at some point
+ (and to justify the amount of time i spent here).
+*/
+export class QuadTree implements RefreshingCollisionFinderBehavior {
+ private static readonly QUADTREE_MAX_LEVELS = 3;
+ private static readonly QUADTREE_SPLIT_THRESHOLD = 2000;
+
private maxLevels: number;
private splitThreshold: number;
private level: number;
@@ -24,34 +26,33 @@ export class QuadTree {
private objects: BoxedEntry[];
constructor(
- topLeft: Coord2D,
+ topLeft: Coord2D = { x: 0, y: 0 },
dimension: Dimension2D,
- maxLevels: number,
- splitThreshold: number,
- level?: number,
+ maxLevels: number = QuadTree.QUADTREE_MAX_LEVELS,
+ splitThreshold: number = QuadTree.QUADTREE_SPLIT_THRESHOLD,
+ level: number = 0
) {
this.children = new Map<Quadrant, QuadTree>();
this.objects = [];
this.maxLevels = maxLevels;
this.splitThreshold = splitThreshold;
- this.level = level ?? 0;
+ this.level = level;
this.topLeft = topLeft;
this.dimension = dimension;
}
- public insert(id: number, dimension: Dimension2D, center: Coord2D): void {
- const box: BoxedEntry = { id, center, dimension };
+ public insert(boxedEntry: BoxedEntry): void {
if (this.hasChildren()) {
- this.getQuadrants(box).forEach((quadrant) => {
+ this.getQuadrants(boxedEntry).forEach((quadrant) => {
const quadrantBox = this.children.get(quadrant);
- quadrantBox?.insert(id, dimension, center);
+ quadrantBox!.insert(boxedEntry);
});
return;
}
- this.objects.push({ id, dimension, center });
+ this.objects.push(boxedEntry);
if (
this.objects.length > this.splitThreshold &&
@@ -66,22 +67,24 @@ export class QuadTree {
public clear(): void {
this.objects = [];
+
if (this.hasChildren()) {
this.children.forEach((child) => child.clear());
this.children.clear();
}
}
- public getNeighborIds(boxedEntry: BoxedEntry): number[] {
- const neighbors: number[] = this.objects.map(({ id }) => id);
+ public getNeighborIds(boxedEntry: BoxedEntry): Set<string> {
+ const neighbors = new Set<string>(
+ this.objects.map(({ id }) => id).filter((id) => id != boxedEntry.id)
+ );
if (this.hasChildren()) {
this.getQuadrants(boxedEntry).forEach((quadrant) => {
const quadrantBox = this.children.get(quadrant);
-
quadrantBox
?.getNeighborIds(boxedEntry)
- .forEach((id) => neighbors.push(id));
+ .forEach((id) => neighbors.add(id));
});
}
@@ -99,9 +102,9 @@ export class QuadTree {
[Quadrant.III, { x: this.topLeft.x, y: this.topLeft.y + halfHeight }],
[
Quadrant.IV,
- { x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight },
- ],
- ] as [[Quadrant, Coord2D]]
+ { x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight }
+ ]
+ ] as [Quadrant, Coord2D][]
).forEach(([quadrant, pos]) => {
this.children.set(
quadrant,
@@ -110,8 +113,8 @@ export class QuadTree {
{ width: halfWidth, height: halfHeight },
this.maxLevels,
this.splitThreshold,
- this.level + 1,
- ),
+ this.level + 1
+ )
);
});
}
@@ -119,52 +122,48 @@ export class QuadTree {
private getQuadrants(boxedEntry: BoxedEntry): Quadrant[] {
const treeCenter: Coord2D = {
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 (
[
[
Quadrant.I,
- (x: number, y: number) => 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,
+ (x: number, y: number) => x < treeCenter.x && y < treeCenter.y
],
[
Quadrant.III,
- (x: number, y: number) => x < treeCenter.x && y >= treeCenter.y,
+ (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]]
+ (x: number, y: number) => x >= treeCenter.x && y >= treeCenter.y
+ ]
+ ] as [Quadrant, (x: number, y: number) => boolean][]
)
.filter(
([_quadrant, condition]) =>
condition(
boxedEntry.center.x + boxedEntry.dimension.width / 2,
- boxedEntry.center.y + boxedEntry.dimension.height / 2,
+ boxedEntry.center.y + boxedEntry.dimension.height / 2
) ||
condition(
boxedEntry.center.x - boxedEntry.dimension.width / 2,
- boxedEntry.center.y - boxedEntry.dimension.height / 2,
- ),
+ boxedEntry.center.y - boxedEntry.dimension.height / 2
+ )
)
.map(([quadrant]) => quadrant);
}
private realignObjects(): void {
this.objects.forEach((boxedEntry) => {
- this.getQuadrants(boxedEntry).forEach((direction) => {
- const quadrant = this.children.get(direction);
- quadrant?.insert(
- boxedEntry.id,
- boxedEntry.dimension,
- boxedEntry.center,
- );
+ this.getQuadrants(boxedEntry).forEach((quadrant) => {
+ const quadrantBox = this.children.get(quadrant);
+ quadrantBox!.insert(boxedEntry);
});
});
@@ -174,4 +173,12 @@ export class QuadTree {
private hasChildren() {
return this.children && this.children.size > 0;
}
+
+ public setTopLeft(topLeft: Coord2D) {
+ this.topLeft = topLeft;
+ }
+
+ public setDimension(dimension: Dimension2D) {
+ this.dimension = dimension;
+ }
}
diff --git a/engine/structures/RefreshingCollisionFinderBehavior.ts b/engine/structures/RefreshingCollisionFinderBehavior.ts
new file mode 100644
index 0000000..573ddd8
--- /dev/null
+++ b/engine/structures/RefreshingCollisionFinderBehavior.ts
@@ -0,0 +1,14 @@
+import type { Coord2D, Dimension2D } from '../interfaces';
+
+export interface BoxedEntry {
+ id: string;
+ dimension: Dimension2D;
+ center: Coord2D;
+}
+
+export interface RefreshingCollisionFinderBehavior {
+ clear(): void;
+ insert(boxedEntry: BoxedEntry): void;
+ getNeighborIds(boxedEntry: BoxedEntry): Set<string>;
+ setTopLeft(topLeft: Coord2D): void;
+}
diff --git a/engine/structures/index.ts b/engine/structures/index.ts
index 605a82a..679dbd4 100644
--- a/engine/structures/index.ts
+++ b/engine/structures/index.ts
@@ -1 +1,3 @@
-export * from "./QuadTree";
+export * from './RefreshingCollisionFinderBehavior';
+export * from './QuadTree';
+export * from './Grid';
diff --git a/engine/systems/Collision.ts b/engine/systems/Collision.ts
index 2bba03b..4a838dd 100644
--- a/engine/systems/Collision.ts
+++ b/engine/systems/Collision.ts
@@ -1,61 +1,59 @@
-import { SystemNames, System } from ".";
+import { SystemNames, System } from '.';
import {
Mass,
BoundingBox,
ComponentNames,
Jump,
Velocity,
- Forces,
-} from "../components";
-import { Game } from "../Game";
-import { PhysicsConstants } from "../config";
-import { Entity } from "../entities";
-import type { Dimension2D } from "../interfaces";
-import { QuadTree } from "../structures";
+ Forces
+} from '../components';
+import { Game } from '../Game';
+import { Miscellaneous, PhysicsConstants } from '../config';
+import { Entity } from '../entities';
+import type { Coord2D, Dimension2D, Velocity2D } from '../interfaces';
+import { BoxedEntry, RefreshingCollisionFinderBehavior } from '../structures';
export class Collision extends System {
private static readonly COLLIDABLE_COMPONENT_NAMES = [
ComponentNames.Collide,
- ComponentNames.TopCollidable,
+ ComponentNames.TopCollidable
];
- private static readonly QUADTREE_MAX_LEVELS = 10;
- private static readonly QUADTREE_SPLIT_THRESHOLD = 10;
- private quadTree: QuadTree;
+ private collisionFinder: RefreshingCollisionFinderBehavior;
- constructor(screenDimensions: Dimension2D) {
+ constructor(refreshingCollisionFinder: RefreshingCollisionFinderBehavior) {
super(SystemNames.Collision);
- this.quadTree = new QuadTree(
- { x: 0, y: 0 },
- screenDimensions,
- Collision.QUADTREE_MAX_LEVELS,
- Collision.QUADTREE_SPLIT_THRESHOLD,
- );
+ this.collisionFinder = refreshingCollisionFinder;
}
public update(_dt: number, game: Game) {
- // rebuild the quadtree
- this.quadTree.clear();
+ this.collisionFinder.clear();
- const entitiesToAddToQuadtree: Entity[] = [];
+ const entitiesToAddToCollisionFinder: Entity[] = [];
Collision.COLLIDABLE_COMPONENT_NAMES.map((componentName) =>
- game.componentEntities.get(componentName),
- ).forEach(
- (entityIds?: Set<number>) =>
- entityIds?.forEach((id) => {
- const entity = game.entities.get(id);
- if (!entity || !entity.hasComponent(ComponentNames.BoundingBox)) {
- return;
- }
- entitiesToAddToQuadtree.push(entity);
- }),
+ game.forEachEntityWithComponent(componentName, (entity) => {
+ if (!entity.hasComponent(ComponentNames.BoundingBox)) {
+ return;
+ }
+ entitiesToAddToCollisionFinder.push(entity);
+ })
);
- entitiesToAddToQuadtree.forEach((entity) => {
+ this.insertEntitiesAndUpdateBounds(entitiesToAddToCollisionFinder);
+ this.findCollidingEntitiesAndCollide(entitiesToAddToCollisionFinder, game);
+ }
+
+ private insertEntitiesAndUpdateBounds(entities: Entity[]) {
+ const collisionFinderInsertions: BoxedEntry[] = [];
+
+ const topLeft: Coord2D = { x: Infinity, y: Infinity };
+ const bottomRight: Coord2D = { x: -Infinity, y: -Infinity };
+
+ entities.forEach((entity) => {
const boundingBox = entity.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
let dimension = { ...boundingBox.dimension };
@@ -63,18 +61,43 @@ export class Collision extends System {
dimension = boundingBox.getOutscribedBoxDims();
}
- this.quadTree.insert(entity.id, dimension, boundingBox.center);
+ const { center } = boundingBox;
+ const topLeftBoundingBox = boundingBox.getTopLeft();
+ const bottomRightBoundingBox = boundingBox.getBottomRight();
+
+ topLeft.x = Math.min(topLeftBoundingBox.x, topLeft.x);
+ topLeft.y = Math.min(topLeftBoundingBox.y, topLeft.y);
+ bottomRight.x = Math.max(bottomRightBoundingBox.x, bottomRight.x);
+ bottomRight.y = Math.max(bottomRightBoundingBox.y, bottomRight.y);
+
+ collisionFinderInsertions.push({
+ id: entity.id,
+ dimension,
+ center
+ });
});
- // find colliding entities and perform collisions
- const collidingEntities = this.getCollidingEntities(
- entitiesToAddToQuadtree,
- game,
+ // set bounds first
+ if (entities.length > 0) {
+ this.collisionFinder.setTopLeft(topLeft);
+ this.collisionFinder.setDimension({
+ width: bottomRight.x - topLeft.x,
+ height: bottomRight.y - topLeft.y
+ });
+ }
+
+ // then, begin insertions
+ collisionFinderInsertions.forEach((boxedEntry: BoxedEntry) =>
+ this.collisionFinder.insert(boxedEntry)
);
+ }
+
+ private findCollidingEntitiesAndCollide(entities: Entity[], game: Game) {
+ const collidingEntities = this.getCollidingEntities(entities, game);
collidingEntities.forEach(([entityAId, entityBId]) => {
const [entityA, entityB] = [entityAId, entityBId].map((id) =>
- game.entities.get(id),
+ game.entities.get(id)
);
if (entityA && entityB) {
this.performCollision(entityA, entityB);
@@ -84,12 +107,14 @@ export class Collision extends System {
private performCollision(entityA: Entity, entityB: Entity) {
const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map(
- (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox),
+ (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox)
);
- let velocity = new Velocity();
+ let velocity: Velocity2D = { dCartesian: { dx: 0, dy: 0 }, dTheta: 0 };
if (entityA.hasComponent(ComponentNames.Velocity)) {
- velocity = entityA.getComponent<Velocity>(ComponentNames.Velocity);
+ velocity = entityA.getComponent<Velocity>(
+ ComponentNames.Velocity
+ ).velocity;
}
if (
@@ -100,7 +125,7 @@ export class Collision extends System {
) {
if (entityBBoundingBox.rotation != 0) {
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.`
);
}
@@ -114,7 +139,7 @@ export class Collision extends System {
entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({
fCartesian: { fy: F_n, fx: 0 },
- torque: 0,
+ torque: 0
});
}
@@ -132,35 +157,33 @@ export class Collision extends System {
private getCollidingEntities(
collidableEntities: Entity[],
- game: Game,
- ): [number, number][] {
- const collidingEntityIds: [number, number][] = [];
+ game: Game
+ ): [string, string][] {
+ const collidingEntityIds: [string, string][] = [];
for (const entity of collidableEntities) {
const boundingBox = entity.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
- const neighborIds = this.quadTree
- .getNeighborIds({
- id: entity.id,
- dimension: boundingBox.dimension,
- center: boundingBox.center,
- })
- .filter((neighborId) => neighborId != entity.id);
+ const neighborIds = this.collisionFinder.getNeighborIds({
+ id: entity.id,
+ dimension: boundingBox.dimension,
+ center: boundingBox.center
+ });
- neighborIds.forEach((neighborId) => {
+ for (const neighborId of neighborIds) {
const neighbor = game.getEntity(neighborId);
if (!neighbor) return;
const neighborBoundingBox = neighbor.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
if (boundingBox.isCollidingWith(neighborBoundingBox)) {
collidingEntityIds.push([entity.id, neighborId]);
}
- });
+ }
}
return collidingEntityIds;
@@ -169,11 +192,11 @@ export class Collision extends System {
// ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ
private getDyToPushOutOfFloor(
entityBoundingBox: BoundingBox,
- floorBoundingBox: BoundingBox,
+ floorBoundingBox: BoundingBox
): number {
const {
dimension: { width, height },
- center: { x },
+ center: { x }
} = entityBoundingBox;
const outScribedRectangle = entityBoundingBox.getOutscribedBoxDims();
@@ -192,7 +215,7 @@ export class Collision extends System {
if (x >= floorBoundingBox.center.x) {
boundedCollisionX = Math.min(
floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2,
- clippedX,
+ clippedX
);
return (
outScribedRectangle.height / 2 -
@@ -202,7 +225,7 @@ export class Collision extends System {
boundedCollisionX = Math.max(
floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2,
- clippedX,
+ clippedX
);
return (
diff --git a/engine/systems/FacingDirection.ts b/engine/systems/FacingDirection.ts
index 4426ab6..01f32cf 100644
--- a/engine/systems/FacingDirection.ts
+++ b/engine/systems/FacingDirection.ts
@@ -2,10 +2,10 @@ import {
ComponentNames,
Velocity,
FacingDirection as FacingDirectionComponent,
- Control,
-} from "../components";
-import { Game } from "../Game";
-import { System, SystemNames } from "./";
+ Control
+} from '../components';
+import { Game } from '../Game';
+import { System, SystemNames } from './';
export class FacingDirection extends System {
constructor() {
@@ -20,24 +20,27 @@ export class FacingDirection extends System {
return;
}
- const totalVelocity: Velocity = new Velocity();
+ const totalVelocityComponent = new Velocity();
const control = entity.getComponent<Control>(ComponentNames.Control);
- const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
- totalVelocity.add(velocity);
+ const velocity = entity.getComponent<Velocity>(
+ ComponentNames.Velocity
+ ).velocity;
+
+ totalVelocityComponent.add(velocity);
if (control) {
- totalVelocity.add(control.controlVelocity);
+ totalVelocityComponent.add(control.controlVelocityComponent.velocity);
}
const facingDirection = entity.getComponent<FacingDirectionComponent>(
- ComponentNames.FacingDirection,
+ ComponentNames.FacingDirection
);
- if (totalVelocity.dCartesian.dx > 0) {
+ if (totalVelocityComponent.velocity.dCartesian.dx > 0) {
entity.addComponent(facingDirection.facingRightSprite);
- } else if (totalVelocity.dCartesian.dx < 0) {
+ } else if (totalVelocityComponent.velocity.dCartesian.dx < 0) {
entity.addComponent(facingDirection.facingLeftSprite);
}
- },
+ }
);
}
}
diff --git a/engine/systems/Input.ts b/engine/systems/Input.ts
index 4aa9844..9afd1ab 100644
--- a/engine/systems/Input.ts
+++ b/engine/systems/Input.ts
@@ -4,30 +4,117 @@ import {
ComponentNames,
Velocity,
Mass,
- Control,
-} from "../components";
-import { Game } from "../Game";
-import { KeyConstants, PhysicsConstants } from "../config";
-import { Action } from "../interfaces";
-import { System, SystemNames } from "./";
+ Control
+} from '../components';
+import { Game } from '../Game';
+import { KeyConstants, PhysicsConstants } from '../config';
+import { Action } from '../interfaces';
+import { System, SystemNames } from '.';
+import { MessagePublisher, MessageType } from '../network';
+import { Entity } from '../entities';
export class Input extends System {
+ public clientId: string;
+
private keys: Set<string>;
private actionTimeStamps: Map<Action, number>;
+ private messagePublisher?: MessagePublisher;
- constructor() {
+ constructor(clientId: string, messagePublisher?: MessagePublisher) {
super(SystemNames.Input);
- this.keys = new Set<string>();
- this.actionTimeStamps = new Map<Action, number>();
+ this.clientId = clientId;
+ this.keys = new Set();
+ this.actionTimeStamps = new Map();
+
+ this.messagePublisher = messagePublisher;
}
public keyPressed(key: string) {
this.keys.add(key);
+
+ if (this.messagePublisher) {
+ this.messagePublisher.addMessage({
+ type: MessageType.NEW_INPUT,
+ body: key
+ });
+ }
}
public keyReleased(key: string) {
this.keys.delete(key);
+
+ if (this.messagePublisher) {
+ this.messagePublisher.addMessage({
+ type: MessageType.REMOVE_INPUT,
+ body: key
+ });
+ }
+ }
+
+ public update(_dt: number, game: Game) {
+ game.forEachEntityWithComponent(ComponentNames.Control, (entity) =>
+ this.handleInput(entity)
+ );
+ }
+
+ public handleInput(entity: Entity) {
+ const controlComponent = entity.getComponent<Control>(
+ ComponentNames.Control
+ );
+ controlComponent.isControllable =
+ controlComponent.controllableBy === this.clientId;
+
+ if (!controlComponent.isControllable) return;
+
+ if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_RIGHT))) {
+ controlComponent.controlVelocityComponent.velocity.dCartesian.dx +=
+ PhysicsConstants.PLAYER_MOVE_VEL;
+ }
+
+ if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))) {
+ controlComponent.controlVelocityComponent.velocity.dCartesian.dx +=
+ -PhysicsConstants.PLAYER_MOVE_VEL;
+ }
+
+ if (
+ entity.hasComponent(ComponentNames.Jump) &&
+ this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))
+ ) {
+ this.performJump(entity);
+ }
+ }
+
+ private performJump(entity: Entity) {
+ const velocity = entity.getComponent<Velocity>(
+ ComponentNames.Velocity
+ ).velocity;
+ const jump = entity.getComponent<Jump>(ComponentNames.Jump);
+
+ if (jump.canJump) {
+ this.actionTimeStamps.set(Action.JUMP, performance.now());
+
+ velocity.dCartesian.dy += PhysicsConstants.PLAYER_JUMP_INITIAL_VEL;
+ jump.canJump = false;
+ }
+
+ if (
+ performance.now() - (this.actionTimeStamps.get(Action.JUMP) || 0) <
+ PhysicsConstants.MAX_JUMP_TIME_MS
+ ) {
+ const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
+
+ const jumpForce = {
+ fCartesian: {
+ fy: mass * PhysicsConstants.PLAYER_JUMP_ACC,
+ fx: 0
+ },
+ torque: 0
+ };
+ entity
+ .getComponent<Forces>(ComponentNames.Forces)
+ ?.forces.push(jumpForce);
+ }
}
private hasSomeKey(keys?: string[]): boolean {
@@ -36,48 +123,4 @@ export class Input extends System {
}
return false;
}
-
- 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))) {
- control.controlVelocity.dCartesian.dx +=
- PhysicsConstants.PLAYER_MOVE_VEL;
- }
-
- if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))) {
- control.controlVelocity.dCartesian.dx +=
- -PhysicsConstants.PLAYER_MOVE_VEL;
- }
-
- if (entity.hasComponent(ComponentNames.Jump)) {
- const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
- const jump = entity.getComponent<Jump>(ComponentNames.Jump);
-
- if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) {
- if (jump.canJump) {
- this.actionTimeStamps.set(Action.JUMP, performance.now());
-
- velocity.dCartesian.dy += PhysicsConstants.PLAYER_JUMP_INITIAL_VEL;
- jump.canJump = false;
- }
-
- if (
- performance.now() - (this.actionTimeStamps.get(Action.JUMP) || 0) <
- PhysicsConstants.MAX_JUMP_TIME_MS
- ) {
- const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
- entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({
- fCartesian: {
- fy: mass * PhysicsConstants.PLAYER_JUMP_ACC,
- fx: 0,
- },
- torque: 0,
- });
- }
- }
- }
- });
- }
}
diff --git a/engine/systems/NetworkUpdate.ts b/engine/systems/NetworkUpdate.ts
new file mode 100644
index 0000000..6d13574
--- /dev/null
+++ b/engine/systems/NetworkUpdate.ts
@@ -0,0 +1,72 @@
+import { System, SystemNames } from '.';
+import { Game } from '../Game';
+import { ComponentNames } from '../components';
+import {
+ type MessageQueueProvider,
+ type MessagePublisher,
+ type MessageProcessor,
+ MessageType,
+ EntityUpdateBody
+} from '../network';
+
+export class NetworkUpdate extends System {
+ private queueProvider: MessageQueueProvider;
+ private publisher: MessagePublisher;
+ private messageProcessor: MessageProcessor;
+
+ private entityUpdateTimers: Map<string, number>;
+
+ constructor(
+ queueProvider: MessageQueueProvider,
+ publisher: MessagePublisher,
+ messageProcessor: MessageProcessor
+ ) {
+ super(SystemNames.NetworkUpdate);
+
+ this.queueProvider = queueProvider;
+ this.publisher = publisher;
+ this.messageProcessor = messageProcessor;
+
+ this.entityUpdateTimers = new Map();
+ }
+
+ public update(dt: number, game: Game) {
+ // 1. process new messages
+ this.queueProvider
+ .getNewMessages()
+ .forEach((message) => this.messageProcessor.process(message));
+ this.queueProvider.clearMessages();
+
+ // 2. send entity updates
+ const updateMessages: EntityUpdateBody[] = [];
+ game.forEachEntityWithComponent(
+ ComponentNames.NetworkUpdateable,
+ (entity) => {
+ let timer = this.entityUpdateTimers.get(entity.id) ?? dt;
+ timer -= dt;
+ this.entityUpdateTimers.set(entity.id, timer);
+
+ if (timer > 0) return;
+ this.entityUpdateTimers.set(entity.id, this.getNextUpdateTimeMs());
+
+ if (entity.hasComponent(ComponentNames.NetworkUpdateable)) {
+ updateMessages.push({
+ id: entity.id,
+ args: entity.serialize()
+ });
+ }
+ }
+ );
+ this.publisher.addMessage({
+ type: MessageType.UPDATE_ENTITIES,
+ body: updateMessages
+ });
+
+ // 3. publish changes
+ this.publisher.publish();
+ }
+
+ private getNextUpdateTimeMs() {
+ return Math.random() * 70 + 50;
+ }
+}
diff --git a/engine/systems/Physics.ts b/engine/systems/Physics.ts
index 38962a6..b5df459 100644
--- a/engine/systems/Physics.ts
+++ b/engine/systems/Physics.ts
@@ -1,4 +1,4 @@
-import { System, SystemNames } from ".";
+import { System, SystemNames } from '.';
import {
BoundingBox,
ComponentNames,
@@ -8,11 +8,11 @@ import {
Mass,
Jump,
Moment,
- Control,
-} from "../components";
-import { PhysicsConstants } from "../config";
-import type { Force2D } from "../interfaces";
-import { Game } from "../Game";
+ Control
+} from '../components';
+import { PhysicsConstants } from '../config';
+import type { Force2D, Velocity2D } from '../interfaces';
+import { Game } from '../Game';
export class Physics extends System {
constructor() {
@@ -23,9 +23,11 @@ export class Physics extends System {
game.forEachEntityWithComponent(ComponentNames.Forces, (entity) => {
const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces;
- const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+ const velocity = entity.getComponent<Velocity>(
+ ComponentNames.Velocity
+ ).velocity;
const inertia = entity.getComponent<Moment>(
- ComponentNames.Moment,
+ ComponentNames.Moment
).inertia;
// F_g = mg, applied only until terminal velocity is reached
@@ -35,9 +37,9 @@ export class Physics extends System {
forces.push({
fCartesian: {
fy: mass * PhysicsConstants.GRAVITY,
- fx: 0,
+ fx: 0
},
- torque: 0,
+ torque: 0
});
}
}
@@ -47,17 +49,17 @@ export class Physics extends System {
(accum: Force2D, { fCartesian, torque }: Force2D) => ({
fCartesian: {
fx: accum.fCartesian.fx + (fCartesian?.fx ?? 0),
- fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0),
+ fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0)
},
- 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
const [ddy, ddx] = [
sumOfForces.fCartesian.fy,
- sumOfForces.fCartesian.fx,
+ sumOfForces.fCartesian.fx
].map((x) => x / mass);
velocity.dCartesian.dx += ddx * dt;
velocity.dCartesian.dy += ddy * dt;
@@ -73,30 +75,32 @@ export class Physics extends System {
});
game.forEachEntityWithComponent(ComponentNames.Velocity, (entity) => {
- const velocity: Velocity = new Velocity();
+ const velocityComponent: Velocity = new Velocity();
const control = entity.getComponent<Control>(ComponentNames.Control);
- velocity.add(entity.getComponent<Velocity>(ComponentNames.Velocity));
+ velocityComponent.add(
+ entity.getComponent<Velocity>(ComponentNames.Velocity).velocity
+ );
if (control) {
- velocity.add(control.controlVelocity);
+ velocityComponent.add(control.controlVelocityComponent.velocity);
}
const boundingBox = entity.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
// integrate velocity
- boundingBox.center.x += velocity.dCartesian.dx * dt;
- boundingBox.center.y += velocity.dCartesian.dy * dt;
- boundingBox.rotation += velocity.dTheta * dt;
+ boundingBox.center.x += velocityComponent.velocity.dCartesian.dx * dt;
+ boundingBox.center.y += velocityComponent.velocity.dCartesian.dy * dt;
+ boundingBox.rotation += velocityComponent.velocity.dTheta * dt;
boundingBox.rotation =
(boundingBox.rotation < 0
? 360 + boundingBox.rotation
: boundingBox.rotation) % 360;
// clear the control velocity
- if (control) {
- control.controlVelocity = new Velocity();
+ if (control && control.isControllable) {
+ control.controlVelocityComponent = new Velocity();
}
});
}
diff --git a/engine/systems/Render.ts b/engine/systems/Render.ts
index 9bb4091..4a4500d 100644
--- a/engine/systems/Render.ts
+++ b/engine/systems/Render.ts
@@ -1,7 +1,7 @@
-import { System, SystemNames } from ".";
-import { BoundingBox, ComponentNames, Sprite } from "../components";
-import { Game } from "../Game";
-import { clamp } from "../utils";
+import { System, SystemNames } from '.';
+import { BoundingBox, ComponentNames, Sprite } from '../components';
+import { Game } from '../Game';
+import { clamp } from '../utils';
export class Render extends System {
private ctx: CanvasRenderingContext2D;
@@ -19,7 +19,7 @@ export class Render extends System {
sprite.update(dt);
const boundingBox = entity.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
// don't render if we're outside the screen
@@ -27,12 +27,12 @@ export class Render extends System {
clamp(
boundingBox.center.y,
-boundingBox.dimension.height / 2,
- this.ctx.canvas.height + boundingBox.dimension.height / 2,
+ this.ctx.canvas.height + boundingBox.dimension.height / 2
) != boundingBox.center.y ||
clamp(
boundingBox.center.x,
-boundingBox.dimension.width / 2,
- this.ctx.canvas.width + boundingBox.dimension.width / 2,
+ this.ctx.canvas.width + boundingBox.dimension.width / 2
) != boundingBox.center.x
) {
return;
@@ -41,7 +41,7 @@ export class Render extends System {
const drawArgs = {
center: boundingBox.center,
dimension: boundingBox.dimension,
- rotation: boundingBox.rotation,
+ rotation: boundingBox.rotation
};
sprite.draw(this.ctx, drawArgs);
diff --git a/engine/systems/System.ts b/engine/systems/System.ts
index 8b00dc5..de41988 100644
--- a/engine/systems/System.ts
+++ b/engine/systems/System.ts
@@ -1,4 +1,4 @@
-import { Game } from "../Game";
+import { Game } from '../Game';
export abstract class System {
public readonly name: string;
diff --git a/engine/systems/WallBounds.ts b/engine/systems/WallBounds.ts
index a0d4a9c..7da84e4 100644
--- a/engine/systems/WallBounds.ts
+++ b/engine/systems/WallBounds.ts
@@ -1,28 +1,24 @@
-import { System, SystemNames } from ".";
-import { BoundingBox, ComponentNames } from "../components";
-import { Game } from "../Game";
-import type { Entity } from "../entities";
-import { clamp } from "../utils";
+import { System, SystemNames } from '.';
+import { BoundingBox, ComponentNames } from '../components';
+import { Game } from '../Game';
+import { clamp } from '../utils';
+import { Miscellaneous } from '../config';
export class WallBounds extends System {
- private screenWidth: number;
-
- constructor(screenWidth: number) {
+ constructor() {
super(SystemNames.WallBounds);
-
- this.screenWidth = screenWidth;
}
public update(_dt: number, game: Game) {
game.forEachEntityWithComponent(ComponentNames.WallBounded, (entity) => {
const boundingBox = entity.getComponent<BoundingBox>(
- ComponentNames.BoundingBox,
+ ComponentNames.BoundingBox
);
boundingBox.center.x = clamp(
boundingBox.center.x,
boundingBox.dimension.width / 2,
- this.screenWidth - boundingBox.dimension.width / 2,
+ Miscellaneous.WIDTH - boundingBox.dimension.width / 2
);
});
}
diff --git a/engine/systems/index.ts b/engine/systems/index.ts
index 6cb6f35..43181e9 100644
--- a/engine/systems/index.ts
+++ b/engine/systems/index.ts
@@ -1,8 +1,9 @@
-export * from "./names";
-export * from "./System";
-export * from "./Render";
-export * from "./Physics";
-export * from "./Input";
-export * from "./FacingDirection";
-export * from "./Collision";
-export * from "./WallBounds";
+export * from './names';
+export * from './System';
+export * from './Render';
+export * from './Physics';
+export * from './Input';
+export * from './FacingDirection';
+export * from './Collision';
+export * from './WallBounds';
+export * from './NetworkUpdate';
diff --git a/engine/systems/names.ts b/engine/systems/names.ts
index 23f31fc..ddf6f19 100644
--- a/engine/systems/names.ts
+++ b/engine/systems/names.ts
@@ -1,8 +1,9 @@
export namespace SystemNames {
- export const Render = "Render";
- export const Physics = "Physics";
- export const FacingDirection = "FacingDirection";
- export const Input = "Input";
- export const Collision = "Collision";
- export const WallBounds = "WallBounds";
+ export const Render = 'Render';
+ export const Physics = 'Physics';
+ export const FacingDirection = 'FacingDirection';
+ export const Input = 'Input';
+ export const Collision = 'Collision';
+ export const WallBounds = 'WallBounds';
+ export const NetworkUpdate = 'NetworkUpdate';
}
diff --git a/engine/utils/coding.ts b/engine/utils/coding.ts
new file mode 100644
index 0000000..3f78889
--- /dev/null
+++ b/engine/utils/coding.ts
@@ -0,0 +1,27 @@
+const replacer = (_key: any, value: any) => {
+ if (value instanceof Map) {
+ return {
+ dataType: 'Map',
+ value: Array.from(value.entries())
+ };
+ } else {
+ return value;
+ }
+};
+
+const reviver = (_key: any, value: any) => {
+ if (typeof value === 'object' && value !== null) {
+ if (value.dataType === 'Map') {
+ return new Map(value.value);
+ }
+ }
+ return value;
+};
+
+export const stringify = (obj: any) => {
+ return JSON.stringify(obj, replacer);
+};
+
+export const parse = <T>(str: string) => {
+ return JSON.parse(str, reviver) as unknown as T;
+};
diff --git a/engine/utils/dotProduct.ts b/engine/utils/dotProduct.ts
index 59f8857..82bcdea 100644
--- a/engine/utils/dotProduct.ts
+++ b/engine/utils/dotProduct.ts
@@ -1,4 +1,4 @@
-import type { Coord2D } from "../interfaces";
+import type { Coord2D } from '../interfaces';
export const dotProduct = (vector1: Coord2D, vector2: Coord2D): number =>
vector1.x * vector2.x + vector1.y * vector2.y;
diff --git a/engine/utils/index.ts b/engine/utils/index.ts
index 82a0d05..65446d1 100644
--- a/engine/utils/index.ts
+++ b/engine/utils/index.ts
@@ -1,3 +1,4 @@
-export * from "./rotateVector";
-export * from "./dotProduct";
-export * from "./clamp";
+export * from './rotateVector';
+export * from './dotProduct';
+export * from './clamp';
+export * from './coding';
diff --git a/engine/utils/rotateVector.ts b/engine/utils/rotateVector.ts
index 82bb54d..221ffb2 100644
--- a/engine/utils/rotateVector.ts
+++ b/engine/utils/rotateVector.ts
@@ -1,4 +1,4 @@
-import type { Coord2D } from "../interfaces";
+import type { Coord2D } from '../interfaces';
/**
* ([[cos(θ), -sin(θ),]) ([x,)
@@ -10,6 +10,6 @@ export const rotateVector = (vector: Coord2D, theta: number): Coord2D => {
return {
x: vector.x * cos - vector.y * sin,
- y: vector.x * sin + vector.y * cos,
+ y: vector.x * sin + vector.y * cos
};
};