summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/components/BoundingBox.ts4
-rw-r--r--engine/components/Control.ts2
-rw-r--r--engine/components/NetworkUpdateable.ts8
-rw-r--r--engine/config/constants.ts10
-rw-r--r--engine/entities/Entity.ts32
-rw-r--r--engine/entities/Floor.ts24
-rw-r--r--engine/entities/Player.ts44
-rw-r--r--engine/network/index.ts12
-rw-r--r--engine/systems/Input.ts144
-rw-r--r--engine/systems/NetworkUpdate.ts40
-rw-r--r--engine/systems/Physics.ts2
11 files changed, 229 insertions, 93 deletions
diff --git a/engine/components/BoundingBox.ts b/engine/components/BoundingBox.ts
index dbe083e..921feb9 100644
--- a/engine/components/BoundingBox.ts
+++ b/engine/components/BoundingBox.ts
@@ -15,7 +15,6 @@ 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();
@@ -36,6 +35,7 @@ export class BoundingBox extends Component {
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++) {
@@ -89,6 +89,8 @@ 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)),
diff --git a/engine/components/Control.ts b/engine/components/Control.ts
index beec82c..d3987d7 100644
--- a/engine/components/Control.ts
+++ b/engine/components/Control.ts
@@ -3,6 +3,7 @@ import { Component, ComponentNames, Velocity } from '.';
export class Control extends Component {
public controlVelocityComponent: Velocity;
public controllableBy: string;
+ public isControllable: boolean; // computed each update in the input system
constructor(
controllableBy: string,
@@ -12,5 +13,6 @@ export class Control extends Component {
this.controllableBy = controllableBy;
this.controlVelocityComponent = controlVelocityComponent;
+ this.isControllable = false;
}
}
diff --git a/engine/components/NetworkUpdateable.ts b/engine/components/NetworkUpdateable.ts
index 7fb6d18..014270c 100644
--- a/engine/components/NetworkUpdateable.ts
+++ b/engine/components/NetworkUpdateable.ts
@@ -1,13 +1,7 @@
import { Component, ComponentNames } from '.';
export class NetworkUpdateable extends Component {
- public isPublish: boolean;
- public isSubscribe: boolean;
-
- constructor(isPublish: boolean, isSubscribe: boolean) {
+ constructor() {
super(ComponentNames.NetworkUpdateable);
-
- this.isPublish = isPublish;
- this.isSubscribe = isSubscribe;
}
}
diff --git a/engine/config/constants.ts b/engine/config/constants.ts
index 45b0301..dc98ad0 100644
--- a/engine/config/constants.ts
+++ b/engine/config/constants.ts
@@ -3,13 +3,13 @@ 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
};
@@ -18,7 +18,7 @@ export namespace KeyConstants {
export const ActionKeys: Map<Action, string[]> = Object.keys(
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);
@@ -33,7 +33,7 @@ export namespace KeyConstants {
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;
}
diff --git a/engine/entities/Entity.ts b/engine/entities/Entity.ts
index b016fc0..63fb370 100644
--- a/engine/entities/Entity.ts
+++ b/engine/entities/Entity.ts
@@ -1,12 +1,15 @@
-import { EntityNames, Player } from '.';
-import type { Component } from '../components';
+import { EntityNames, Floor, Player } from '.';
+import { type Component } from '../components';
+
+const randomId = () =>
+ (performance.now() + Math.random() * 10_000_000).toString();
export abstract class Entity {
public id: string;
public components: Map<string, Component>;
public name: string;
- constructor(name: string, id: string = crypto.randomUUID()) {
+ constructor(name: string, id: string = randomId()) {
this.name = name;
this.id = id;
this.components = new Map();
@@ -31,14 +34,29 @@ export abstract class Entity {
return this.components.has(name);
}
- static from(entityName: string, args: any): Entity {
+ public static from(entityName: string, id: string, args: any): Entity {
+ let entity: Entity;
+
switch (entityName) {
case EntityNames.Player:
- const player = new Player(args.playerId);
- player.id = args.id;
- return 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 6f9b13b..b4f48e5 100644
--- a/engine/entities/Floor.ts
+++ b/engine/entities/Floor.ts
@@ -1,5 +1,5 @@
import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from '../config';
-import { BoundingBox, Sprite } from '../components';
+import { BoundingBox, ComponentNames, Sprite } from '../components';
import { TopCollidable } from '../components/TopCollidable';
import { Entity, EntityNames } from '../entities';
@@ -8,9 +8,13 @@ export class Floor extends Entity {
Sprites.FLOOR
) as SpriteSpec;
+ private width: number;
+
constructor(width: number) {
super(EntityNames.Floor);
+ this.width = width;
+
this.addComponent(
new Sprite(
IMAGES.get((Floor.spriteSpec?.states?.get(width) as SpriteSpec).sheet),
@@ -23,4 +27,22 @@ export class Floor extends Entity {
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(
+ boundingBox.center,
+ boundingBox.dimension,
+ boundingBox.rotation
+ )
+ );
+ }
}
diff --git a/engine/entities/Player.ts b/engine/entities/Player.ts
index 947fbd6..4d91c6f 100644
--- a/engine/entities/Player.ts
+++ b/engine/entities/Player.ts
@@ -10,9 +10,10 @@ import {
WallBounded,
Forces,
Collide,
- Control,
Mass,
- Moment
+ Moment,
+ ComponentNames,
+ Control
} from '../components';
import { Direction } from '../interfaces';
@@ -24,14 +25,14 @@ export class Player extends Entity {
Sprites.COFFEE
) as SpriteSpec;
- constructor(playerId: string) {
+ constructor() {
super(EntityNames.Player);
this.addComponent(
new BoundingBox(
{
- x: 300,
- y: 100
+ x: 0,
+ y: 0
},
{ width: Player.spriteSpec.width, height: Player.spriteSpec.height },
0
@@ -48,7 +49,6 @@ export class Player extends Entity {
this.addComponent(new Gravity());
this.addComponent(new Jump());
- this.addComponent(new Control(playerId));
this.addComponent(new Collide());
this.addComponent(new WallBounded());
@@ -69,6 +69,36 @@ export class Player extends Entity {
);
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/network/index.ts b/engine/network/index.ts
index 1bf95fb..5dc7ece 100644
--- a/engine/network/index.ts
+++ b/engine/network/index.ts
@@ -1,12 +1,20 @@
export enum MessageType {
NEW_ENTITIES = 'NEW_ENTITIES',
REMOVE_ENTITIES = 'REMOVE_ENTITIES',
- UPDATE_ENTITY = 'UPDATE_ENTITY'
+ UPDATE_ENTITIES = 'UPDATE_ENTITIES',
+ NEW_INPUT = 'NEW_INPUT',
+ REMOVE_INPUT = 'REMOVE_INPUT'
}
export type EntityAddBody = {
entityName: string;
- args: any;
+ id: string;
+ args: Record<string, any>;
+};
+
+export type EntityUpdateBody = {
+ id: string;
+ args: Record<string, any>;
};
export type Message = {
diff --git a/engine/systems/Input.ts b/engine/systems/Input.ts
index 8a68905..9afd1ab 100644
--- a/engine/systems/Input.ts
+++ b/engine/systems/Input.ts
@@ -10,26 +10,111 @@ 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(clientId: string) {
+ constructor(clientId: string, messagePublisher?: MessagePublisher) {
super(SystemNames.Input);
this.clientId = clientId;
- this.keys = new Set<string>();
- this.actionTimeStamps = new Map<Action, number>();
+ 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 {
@@ -38,57 +123,4 @@ export class Input extends System {
}
return false;
}
-
- public update(_dt: number, game: Game) {
- game.forEachEntityWithComponent(ComponentNames.Control, (entity) => {
- const controlComponent = entity.getComponent<Control>(
- ComponentNames.Control
- );
- if (controlComponent.controllableBy != this.clientId) 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)) {
- const velocity = entity.getComponent<Velocity>(
- ComponentNames.Velocity
- ).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;
-
- const jumpForce = {
- fCartesian: {
- fy: mass * PhysicsConstants.PLAYER_JUMP_ACC,
- fx: 0
- },
- torque: 0
- };
- entity
- .getComponent<Forces>(ComponentNames.Forces)
- ?.forces.push(jumpForce);
- }
- }
- }
- });
- }
}
diff --git a/engine/systems/NetworkUpdate.ts b/engine/systems/NetworkUpdate.ts
index bcfb71e..6d13574 100644
--- a/engine/systems/NetworkUpdate.ts
+++ b/engine/systems/NetworkUpdate.ts
@@ -1,10 +1,12 @@
import { System, SystemNames } from '.';
import { Game } from '../Game';
-import { ComponentNames, NetworkUpdateable } from '../components';
+import { ComponentNames } from '../components';
import {
type MessageQueueProvider,
type MessagePublisher,
- type MessageProcessor
+ type MessageProcessor,
+ MessageType,
+ EntityUpdateBody
} from '../network';
export class NetworkUpdate extends System {
@@ -12,6 +14,8 @@ export class NetworkUpdate extends System {
private publisher: MessagePublisher;
private messageProcessor: MessageProcessor;
+ private entityUpdateTimers: Map<string, number>;
+
constructor(
queueProvider: MessageQueueProvider,
publisher: MessagePublisher,
@@ -22,23 +26,47 @@ export class NetworkUpdate extends System {
this.queueProvider = queueProvider;
this.publisher = publisher;
this.messageProcessor = messageProcessor;
+
+ this.entityUpdateTimers = new Map();
}
- public update(_dt: number, game: Game) {
+ 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) => {
- const networkUpdateComponent = entity.getComponent<NetworkUpdateable>(
- ComponentNames.NetworkUpdateable
- );
+ 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 35afb3f..b5df459 100644
--- a/engine/systems/Physics.ts
+++ b/engine/systems/Physics.ts
@@ -99,7 +99,7 @@ export class Physics extends System {
: boundingBox.rotation) % 360;
// clear the control velocity
- if (control) {
+ if (control && control.isControllable) {
control.controlVelocityComponent = new Velocity();
}
});