Add better object updates

This commit is contained in:
schmelczerandras 2020-10-17 18:35:09 +02:00
parent fd80a299b6
commit e83c58e1a5
29 changed files with 289 additions and 123 deletions

View file

@ -1,7 +1,13 @@
import { PhysicalContainer } from './physics/containers/physical-container'; import { PhysicalContainer } from './physics/containers/physical-container';
import { Player } from './players/player'; import { Player } from './players/player';
import ioserver from 'socket.io'; import ioserver from 'socket.io';
import { TransportEvents, deserialize, settings, ServerInformation } from 'shared'; import {
TransportEvents,
deserialize,
settings,
ServerInformation,
PlayerInformation,
} from 'shared';
import { createWorld } from './map/create-world'; import { createWorld } from './map/create-world';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { Options } from './options'; import { Options } from './options';
@ -22,8 +28,8 @@ export class GameServer {
this.objects.initialize(); this.objects.initialize();
io.on('connection', (socket: SocketIO.Socket) => { io.on('connection', (socket: SocketIO.Socket) => {
socket.on(TransportEvents.PlayerJoining, () => { socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => {
const player = new Player(this.objects, socket); const player = new Player(playerInfo, this.objects, socket);
this.players.push(player); this.players.push(player);
socket.on(TransportEvents.PlayerToServer, (json: string) => { socket.on(TransportEvents.PlayerToServer, (json: string) => {
const command = deserialize(json); const command = deserialize(json);

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Circle, GameObject, serializesTo, settings } from 'shared'; import { Circle, GameObject, serializesTo, settings, UpdateObjectMessage } from 'shared';
import { PhysicalBase } from '../physics/physicals/physical-base'; import { PhysicalBase } from '../physics/physicals/physical-base';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
@ -30,6 +30,10 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio
this.recalculateBoundingBox(); this.recalculateBoundingBox();
} }
public calculateUpdates(): UpdateObjectMessage | null {
return null;
}
public get boundingBox(): BoundingBoxBase { public get boundingBox(): BoundingBoxBase {
return this._boundingBox; return this._boundingBox;
} }

View file

@ -1,7 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { import {
id, id,
CharacterBase,
settings, settings,
MoveActionCommand, MoveActionCommand,
serializesTo, serializesTo,
@ -9,6 +8,7 @@ import {
last, last,
GameObject, GameObject,
Circle, Circle,
PlayerCharacterBase,
} from 'shared'; } from 'shared';
import { DynamicPhysical } from '../physics/physicals/dynamic-physical'; import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
import { CirclePhysical } from './circle-physical'; import { CirclePhysical } from './circle-physical';
@ -20,10 +20,12 @@ import { forceAtPosition } from '../physics/functions/force-at-position';
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle'; import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
import { PlanetPhysical } from './planet-physical'; import { PlanetPhysical } from './planet-physical';
import { ReactsToCollision } from '../physics/physicals/reacts-to-collision'; import { ReactsToCollision } from '../physics/physicals/reacts-to-collision';
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { UpdateGameObjectMessage } from '../update-game-object-message';
@serializesTo(CharacterBase) @serializesTo(PlayerCharacterBase)
export class CharacterPhysical export class PlayerCharacterPhysical
extends CharacterBase extends PlayerCharacterBase
implements DynamicPhysical, ReactsToCollision { implements DynamicPhysical, ReactsToCollision {
public readonly canCollide = true; public readonly canCollide = true;
public readonly canMove = true; public readonly canMove = true;
@ -40,32 +42,32 @@ export class CharacterPhysical
vec2.create(), vec2.create(),
vec2.add( vec2.add(
vec2.create(), vec2.create(),
CharacterPhysical.desiredHeadOffset, PlayerCharacterPhysical.desiredHeadOffset,
CharacterPhysical.desiredLeftFootOffset, PlayerCharacterPhysical.desiredLeftFootOffset,
), ),
CharacterPhysical.desiredRightFootOffset, PlayerCharacterPhysical.desiredRightFootOffset,
), ),
1 / 3, 1 / 3,
); );
private static readonly headOffset = vec2.subtract( private static readonly headOffset = vec2.subtract(
vec2.create(), vec2.create(),
CharacterPhysical.desiredHeadOffset, PlayerCharacterPhysical.desiredHeadOffset,
CharacterPhysical.centerOfMass, PlayerCharacterPhysical.centerOfMass,
); );
private static readonly leftFootOffset = vec2.subtract( private static readonly leftFootOffset = vec2.subtract(
vec2.create(), vec2.create(),
CharacterPhysical.desiredLeftFootOffset, PlayerCharacterPhysical.desiredLeftFootOffset,
CharacterPhysical.centerOfMass, PlayerCharacterPhysical.centerOfMass,
); );
private static readonly rightFootOffset = vec2.subtract( private static readonly rightFootOffset = vec2.subtract(
vec2.create(), vec2.create(),
CharacterPhysical.desiredRightFootOffset, PlayerCharacterPhysical.desiredRightFootOffset,
CharacterPhysical.centerOfMass, PlayerCharacterPhysical.centerOfMass,
); );
public static readonly boundRadius = public static readonly boundRadius =
(CharacterPhysical.headRadius + CharacterPhysical.feetRadius * 2) * 2; (PlayerCharacterPhysical.headRadius + PlayerCharacterPhysical.feetRadius * 2) * 2;
private isDestroyed = false; private isDestroyed = false;
private direction = 0; private direction = 0;
@ -78,6 +80,10 @@ export class CharacterPhysical
public rightFoot: CirclePhysical; public rightFoot: CirclePhysical;
public bound: CirclePhysical; public bound: CirclePhysical;
public get isAlive(): boolean {
return !this.isDestroyed;
}
private movementActions: Array<MoveActionCommand> = []; private movementActions: Array<MoveActionCommand> = [];
private lastMovementAction: MoveActionCommand = new MoveActionCommand( private lastMovementAction: MoveActionCommand = new MoveActionCommand(
vec2.create(), vec2.create(),
@ -85,26 +91,28 @@ export class CharacterPhysical
); );
constructor( constructor(
name: string,
public readonly colorIndex: number, public readonly colorIndex: number,
private readonly container: PhysicalContainer, private readonly container: PhysicalContainer,
startPosition: vec2, startPosition: vec2,
) { ) {
super(id(), colorIndex); super(id(), name, colorIndex);
this.head = new CirclePhysical( this.head = new CirclePhysical(
vec2.add(vec2.create(), startPosition, CharacterPhysical.headOffset), vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.headOffset),
CharacterPhysical.headRadius, PlayerCharacterPhysical.headRadius,
this, this,
container, container,
); );
this.leftFoot = new CirclePhysical( this.leftFoot = new CirclePhysical(
vec2.add(vec2.create(), startPosition, CharacterPhysical.leftFootOffset), vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.leftFootOffset),
CharacterPhysical.feetRadius, PlayerCharacterPhysical.feetRadius,
this, this,
container, container,
); );
this.rightFoot = new CirclePhysical( this.rightFoot = new CirclePhysical(
vec2.add(vec2.create(), startPosition, CharacterPhysical.rightFootOffset), vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.rightFootOffset),
CharacterPhysical.feetRadius, PlayerCharacterPhysical.feetRadius,
this, this,
container, container,
); );
@ -114,12 +122,16 @@ export class CharacterPhysical
this.bound = new CirclePhysical( this.bound = new CirclePhysical(
vec2.create(), vec2.create(),
CharacterPhysical.boundRadius, PlayerCharacterPhysical.boundRadius,
this, this,
container, container,
); );
} }
public calculateUpdates(): UpdateObjectMessage {
return new UpdateGameObjectMessage(this, ['head', 'leftFoot', 'rightFoot']);
}
public handleMovementAction(c: MoveActionCommand) { public handleMovementAction(c: MoveActionCommand) {
this.movementActions.push(c); this.movementActions.push(c);
} }
@ -189,7 +201,7 @@ export class CharacterPhysical
getBoundingBoxOfCircle( getBoundingBoxOfCircle(
new Circle( new Circle(
this.center, this.center,
CharacterPhysical.boundRadius + settings.maxGravityDistance, PlayerCharacterPhysical.boundRadius + settings.maxGravityDistance,
), ),
), ),
); );
@ -250,9 +262,9 @@ export class CharacterPhysical
const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center); const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center);
vec2.add(bodyCenter, bodyCenter, this.rightFoot.center); vec2.add(bodyCenter, bodyCenter, this.rightFoot.center);
vec2.scale(bodyCenter, bodyCenter, 1 / 3); vec2.scale(bodyCenter, bodyCenter, 1 / 3);
this.springMove(this.leftFoot, bodyCenter, CharacterPhysical.leftFootOffset); this.springMove(this.leftFoot, bodyCenter, PlayerCharacterPhysical.leftFootOffset);
this.springMove(this.rightFoot, bodyCenter, CharacterPhysical.rightFootOffset); this.springMove(this.rightFoot, bodyCenter, PlayerCharacterPhysical.rightFootOffset);
this.springMove(this.head, bodyCenter, CharacterPhysical.headOffset); this.springMove(this.head, bodyCenter, PlayerCharacterPhysical.headOffset);
} }
private springMove(object: CirclePhysical, center: vec2, offset: vec2) { private springMove(object: CirclePhysical, center: vec2, offset: vec2) {

View file

@ -6,6 +6,8 @@ import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { PlanetPhysical } from './planet-physical'; import { PlanetPhysical } from './planet-physical';
import { ReactsToCollision } from '../physics/physicals/reacts-to-collision'; import { ReactsToCollision } from '../physics/physicals/reacts-to-collision';
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { UpdateGameObjectMessage } from '../update-game-object-message';
@serializesTo(ProjectileBase) @serializesTo(ProjectileBase)
export class ProjectilePhysical export class ProjectilePhysical
@ -15,6 +17,7 @@ export class ProjectilePhysical
public readonly canMove = true; public readonly canMove = true;
private isDestroyed = false; private isDestroyed = false;
private bounceCount = 0; private bounceCount = 0;
private _boundingBox?: ImmutableBoundingBox;
public object: CirclePhysical; public object: CirclePhysical;
@ -28,7 +31,9 @@ export class ProjectilePhysical
this.object = new CirclePhysical(center, radius, this, container, 0.9); this.object = new CirclePhysical(center, radius, this, container, 0.9);
} }
private _boundingBox?: ImmutableBoundingBox; public calculateUpdates(): UpdateObjectMessage {
return new UpdateGameObjectMessage(this, ['center']);
}
public get boundingBox(): ImmutableBoundingBox { public get boundingBox(): ImmutableBoundingBox {
if (!this._boundingBox) { if (!this._boundingBox) {

View file

@ -1,4 +1,4 @@
import { DynamicPhysical } from '../dynamic-physical'; import { DynamicPhysical } from '../physicals/dynamic-physical';
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
export class BoundingBoxList { export class BoundingBoxList {

View file

@ -1,5 +1,5 @@
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
import { StaticPhysical } from './static-physical'; import { StaticPhysical } from '../physicals/static-physical';
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js // source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
class Node { class Node {

View file

@ -1,6 +1,9 @@
import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message';
import { PhysicalBase } from './physical-base'; import { PhysicalBase } from './physical-base';
export interface DynamicPhysical extends PhysicalBase { export interface DynamicPhysical extends PhysicalBase {
readonly canMove: true; readonly canMove: true;
step(deltaTimeInMilliseconds: number): void; step(deltaTimeInMilliseconds: number): void;
calculateUpdates(): UpdateObjectMessage | null;
} }

View file

@ -14,27 +14,28 @@ import {
SecondaryActionCommand, SecondaryActionCommand,
settings, settings,
Circle, Circle,
PlayerInformation,
} from 'shared'; } from 'shared';
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds'; import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
import { CharacterPhysical } from '../objects/character-physical';
import { ProjectilePhysical } from '../objects/projectile-physical'; import { ProjectilePhysical } from '../objects/projectile-physical';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { PhysicalContainer } from '../physics/containers/physical-container'; import { PhysicalContainer } from '../physics/containers/physical-container';
import { PhysicalBase } from '../physics/physicals/physical-base';
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle'; import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting'; import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
import { requestColor, freeColor } from './player-color-service'; import { requestColor, freeColor } from './player-color-service';
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
import { DynamicPhysical } from '../physics/physicals/dynamic-physical';
import { Physical } from '../physics/physicals/physical';
export class Player extends CommandReceiver { export class Player extends CommandReceiver {
private character: CharacterPhysical; private character: PlayerCharacterPhysical;
private aspectRatio: number = 16 / 9; private aspectRatio: number = 16 / 9;
private isActive = true; private isActive = true;
private timeSinceLastProjectile = 0; private timeSinceLastProjectile = 0;
private objectsPreviouslyInViewArea: Array<PhysicalBase> = []; private objectsPreviouslyInViewArea: Array<Physical> = [];
private objectsInViewArea: Array<PhysicalBase> = []; private objectsInViewArea: Array<Physical> = [];
private pingTime?: number; private pingTime?: number;
private _latency?: number; private _latency?: number;
@ -56,7 +57,10 @@ export class Player extends CommandReceiver {
[MoveActionCommand.type]: (c: MoveActionCommand) => [MoveActionCommand.type]: (c: MoveActionCommand) =>
this.character.handleMovementAction(c), this.character.handleMovementAction(c),
[SecondaryActionCommand.type]: (c: SecondaryActionCommand) => { [SecondaryActionCommand.type]: (c: SecondaryActionCommand) => {
if (this.timeSinceLastProjectile < settings.projectileCreationInterval) { if (
!this.character.isAlive ||
this.timeSinceLastProjectile < settings.projectileCreationInterval
) {
return; return;
} }
@ -88,7 +92,7 @@ export class Player extends CommandReceiver {
const playerBoundingCircle = new Circle( const playerBoundingCircle = new Circle(
playerPosition, playerPosition,
CharacterPhysical.boundRadius, PlayerCharacterPhysical.boundRadius,
); );
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle); const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
@ -97,27 +101,26 @@ export class Player extends CommandReceiver {
return playerPosition; return playerPosition;
} }
rotation += Math.PI / 12; rotation += Math.PI / 8;
radius += 10; radius += 30;
} }
} }
constructor( constructor(
playerInfo: PlayerInformation,
private readonly objects: PhysicalContainer, private readonly objects: PhysicalContainer,
private readonly socket: SocketIO.Socket, private readonly socket: SocketIO.Socket,
) { ) {
super(); super();
const colorIndex = requestColor(); const colorIndex = requestColor();
this.character = new CharacterPhysical( this.character = new PlayerCharacterPhysical(
playerInfo.name,
colorIndex, colorIndex,
objects, objects,
this.findEmptyPositionForPlayer(), this.findEmptyPositionForPlayer(),
); );
this.objectsPreviouslyInViewArea.push(this.character);
this.objectsInViewArea.push(this.character);
this.objects.addObject(this.character); this.objects.addObject(this.character);
socket.emit( socket.emit(
@ -138,6 +141,7 @@ export class Player extends CommandReceiver {
this.sendObjects(); this.sendObjects();
this.timeSinceLastProjectile += deltaTime; this.timeSinceLastProjectile += deltaTime;
} }
public sendObjects() { public sendObjects() {
const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5); const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5);
const bb = new BoundingBox(); const bb = new BoundingBox();
@ -161,7 +165,11 @@ export class Player extends CommandReceiver {
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
serialize( serialize(
new DeleteObjectsCommand([ new DeleteObjectsCommand([
...new Set(noLongerIntersecting.map((p) => p.gameObject.id)), ...new Set(
noLongerIntersecting
.filter((p) => p.gameObject !== this.character)
.map((p) => p.gameObject.id),
),
]), ]),
), ),
); );
@ -172,20 +180,25 @@ export class Player extends CommandReceiver {
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
serialize( serialize(
new CreateObjectsCommand([ new CreateObjectsCommand([
...new Set(newlyIntersecting.map((p) => p.gameObject)), ...new Set(
newlyIntersecting
.map((p) => p.gameObject)
.filter((g) => g !== this.character),
),
]), ]),
), ),
); );
} }
this.socket.volatile.emit( this.socket.emit(
TransportEvents.ServerToPlayer, TransportEvents.ServerToPlayer,
serialize( serialize(
new UpdateObjectsCommand([ new UpdateObjectsCommand(
...new Set( Array.from(new Set(this.objectsInViewArea))
this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject), .filter((p) => p.canMove)
), .map((p) => (p as DynamicPhysical).calculateUpdates())
]), .filter((p) => p !== null) as any,
),
), ),
); );
} }

View file

@ -0,0 +1,11 @@
import { GameObject, serializesTo, UpdateMessage, UpdateObjectMessage } from 'shared';
@serializesTo(UpdateObjectMessage)
export class UpdateGameObjectMessage<T extends GameObject> extends UpdateObjectMessage {
constructor(object: T, keys: Array<string & keyof T>) {
super(
object.id,
keys.map((k) => new UpdateMessage(k, object[k])),
);
}
}

View file

@ -15,6 +15,9 @@
</head> </head>
<body> <body>
<canvas></canvas>
<div id="overlay"></div>
<article id="landing-ui"> <article id="landing-ui">
<h1>decla.<span class="red">red</span></h1> <h1>decla.<span class="red">red</span></h1>
<form id="join-game-form"> <form id="join-game-form">
@ -41,7 +44,5 @@
</article> </article>
<noscript>Javascript is required for this website.</noscript> <noscript>Javascript is required for this website.</noscript>
<div id="overlay"></div>
<canvas></canvas>
</body> </body>
</html> </html>

View file

@ -1,7 +1,12 @@
import { glMatrix } from 'gl-matrix'; import { glMatrix } from 'gl-matrix';
import { CharacterBase, LampBase, overrideDeserialization, PlanetBase } from 'shared'; import {
import { ProjectileBase } from 'shared/src/objects/types/projectile-base'; CharacterBase,
LampBase,
overrideDeserialization,
PlanetBase,
PlayerCharacterBase,
ProjectileBase,
} from 'shared';
import { CharacterView } from './scripts/objects/character-view'; import { CharacterView } from './scripts/objects/character-view';
import { LampView } from './scripts/objects/lamp-view'; import { LampView } from './scripts/objects/lamp-view';
import { ProjectileView } from './scripts/objects/projectile-view'; import { ProjectileView } from './scripts/objects/projectile-view';
@ -10,43 +15,32 @@ import './styles/main.scss';
import { LandingPageBackground } from './scripts/landing-page-background'; import { LandingPageBackground } from './scripts/landing-page-background';
import { JoinFormHandler } from './scripts/join-form-handler'; import { JoinFormHandler } from './scripts/join-form-handler';
import { Game } from './scripts/game'; import { Game } from './scripts/game';
import { PlayerCharacterView } from './scripts/objects/player-character-view';
glMatrix.setMatrixArrayType(Array); glMatrix.setMatrixArrayType(Array);
overrideDeserialization(CharacterBase, CharacterView); overrideDeserialization(CharacterBase, CharacterView);
overrideDeserialization(PlayerCharacterBase, PlayerCharacterView);
overrideDeserialization(PlanetBase, PlanetView); overrideDeserialization(PlanetBase, PlanetView);
overrideDeserialization(LampBase, LampView); overrideDeserialization(LampBase, LampView);
overrideDeserialization(ProjectileBase, ProjectileView); overrideDeserialization(ProjectileBase, ProjectileView);
const addSupportForTabNavigation = () =>
(document.onkeydown = (e) => {
if (e.key === ' ') {
(document.activeElement as HTMLElement)?.click();
e.preventDefault();
}
});
/*const removeUnnecessaryOutlines = () =>
(document.onclick = (e) => {
(e.target as HTMLElement)?.blur();
});
*/
addSupportForTabNavigation();
//removeUnnecessaryOutlines();
const main = async () => { const main = async () => {
try { try {
const landingUI = document.querySelector('#landing-ui') as HTMLElement; const landingUI = document.querySelector('#landing-ui') as HTMLElement;
const background = new LandingPageBackground(); const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement;
const joinHandler = new JoinFormHandler( const serverContainer = document.querySelector('#server-container') as HTMLElement;
document.querySelector('#join-game-form') as HTMLFormElement, const canvas = document.querySelector('canvas') as HTMLCanvasElement;
document.querySelector('#server-container') as HTMLElement, const overlay = document.querySelector('#overlay') as HTMLElement;
);
const background = new LandingPageBackground(canvas);
const joinHandler = new JoinFormHandler(joinGameForm, serverContainer);
const playerDecision = await joinHandler.getPlayerDecision(); const playerDecision = await joinHandler.getPlayerDecision();
landingUI.style.display = 'none'; landingUI.style.display = 'none';
console.log(playerDecision);
background.destroy(); background.destroy();
await new Game(playerDecision).start(); await new Game(playerDecision, canvas, overlay).start();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert(e); alert(e);

View file

@ -16,6 +16,7 @@ import {
TransportEvents, TransportEvents,
SetAspectRatioActionCommand, SetAspectRatioActionCommand,
rgb, rgb,
PlayerInformation,
} from 'shared'; } from 'shared';
import io from 'socket.io-client'; import io from 'socket.io-client';
import { KeyboardListener } from './commands/generators/keyboard-listener'; import { KeyboardListener } from './commands/generators/keyboard-listener';
@ -32,15 +33,16 @@ import { Polygon } from './shapes/polygon';
export class Game { export class Game {
public readonly gameObjects = new GameObjectContainer(this); public readonly gameObjects = new GameObjectContainer(this);
private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement;
private renderer!: Renderer; private renderer!: Renderer;
private socket!: SocketIOClient.Socket; private socket!: SocketIOClient.Socket;
private promises: Promise<[void, void]>; private promises: Promise<[void, void]>;
private deltaTimeCalculator = new DeltaTimeCalculator(); private deltaTimeCalculator = new DeltaTimeCalculator();
private overlay: HTMLElement = document.querySelector('#overlay') as HTMLDivElement;
constructor(playerDecision: PlayerDecision) { constructor(
console.log(playerDecision.server); private readonly playerDecision: PlayerDecision,
private readonly canvas: HTMLCanvasElement,
private readonly overlay: HTMLElement,
) {
this.promises = Promise.all([ this.promises = Promise.all([
this.setupCommunication(playerDecision.server), this.setupCommunication(playerDecision.server),
this.setupRenderer(), this.setupRenderer(),
@ -66,7 +68,9 @@ export class Game {
this.socket.emit(TransportEvents.Pong); this.socket.emit(TransportEvents.Pong);
}); });
this.socket.emit(TransportEvents.PlayerJoining, null); this.socket.emit(TransportEvents.PlayerJoining, {
name: this.playerDecision.playerName,
} as PlayerInformation);
broadcastCommands( broadcastCommands(
[ [
@ -98,7 +102,7 @@ export class Game {
}, },
{ {
...CircleLight.descriptor, ...CircleLight.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 16], shaderCombinationSteps: [0, 1, 2, 4, 8],
}, },
{ {
...Flashlight.descriptor, ...Flashlight.descriptor,

View file

@ -18,10 +18,9 @@ const LangindPagePolygon = NoisyPolygonFactory(
); );
export class LandingPageBackground { export class LandingPageBackground {
private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement;
private renderer!: Renderer; private renderer!: Renderer;
constructor() { constructor(private readonly canvas: HTMLCanvasElement) {
this.start(); this.start();
} }

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { calculateViewArea, GameObject, mixRgb, settings } from 'shared'; import { calculateViewArea, GameObject, mixRgb, settings, UpdateMessage } from 'shared';
import { Game } from '../game'; import { Game } from '../game';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -14,6 +14,10 @@ export class Camera extends GameObject implements ViewObject {
super(null); super(null);
} }
public update(updates: Array<UpdateMessage>) {
throw new Error();
}
public step(deltaTimeInMilliseconds: number): void {} public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer) { public draw(renderer: Renderer) {

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { CharacterBase, Circle, Id } from 'shared'; import { CharacterBase, Circle, Id, UpdateMessage } from 'shared';
import { BlobShape } from '../shapes/blob-shape'; import { BlobShape } from '../shapes/blob-shape';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -19,6 +19,10 @@ export class CharacterView extends CharacterBase implements ViewObject {
this.shape = new BlobShape(colorIndex); this.shape = new BlobShape(colorIndex);
} }
public update(updates: Array<UpdateMessage>) {
updates.forEach((u) => ((this as any)[u.key] = u.value));
}
public get position(): vec2 { public get position(): vec2 {
return this.head!.center; return this.head!.center;
} }

View file

@ -5,22 +5,23 @@ import {
CreateObjectsCommand, CreateObjectsCommand,
CreatePlayerCommand, CreatePlayerCommand,
DeleteObjectsCommand, DeleteObjectsCommand,
GameObject,
Id, Id,
UpdateObjectsCommand, UpdateObjectsCommand,
} from 'shared'; } from 'shared';
import { Game } from '../game'; import { Game } from '../game';
import { Camera } from './camera'; import { Camera } from './camera';
import { CharacterView } from './character-view'; import { PlayerCharacterView } from './player-character-view';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
export class GameObjectContainer extends CommandReceiver { export class GameObjectContainer extends CommandReceiver {
protected objects: Map<Id, ViewObject> = new Map(); protected objects: Map<Id, ViewObject> = new Map();
public player!: CharacterView; public player!: PlayerCharacterView;
public camera!: Camera; public camera!: Camera;
protected commandExecutors: CommandExecutors = { protected commandExecutors: CommandExecutors = {
[CreatePlayerCommand.type]: (c: CreatePlayerCommand) => { [CreatePlayerCommand.type]: (c: CreatePlayerCommand) => {
this.player = c.character as CharacterView; this.player = c.character as PlayerCharacterView;
this.camera = new Camera(this.game); this.camera = new Camera(this.game);
this.addObject(this.player); this.addObject(this.player);
this.addObject(this.camera); this.addObject(this.camera);
@ -33,13 +34,7 @@ export class GameObjectContainer extends CommandReceiver {
c.ids.forEach((id: Id) => this.objects.delete(id)), c.ids.forEach((id: Id) => this.objects.delete(id)),
[UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => { [UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => {
c.objects.forEach((o) => { c.updates.forEach((u) => this.objects.get(u.id)?.update(u.updates));
this.objects.delete(o.id);
this.addObject(o as ViewObject);
if (o.id === this.player.id) {
this.player = o as CharacterView;
}
});
}, },
}; };

View file

@ -1,6 +1,6 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { CircleLight, Renderer } from 'sdf-2d'; import { CircleLight, Renderer } from 'sdf-2d';
import { CommandExecutors, Id, LampBase } from 'shared'; import { CommandExecutors, Id, LampBase, UpdateMessage } from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -16,6 +16,10 @@ export class LampView extends LampBase implements ViewObject {
this.light = new CircleLight(center, color, lightness); this.light = new CircleLight(center, color, lightness);
} }
public update(message: Array<UpdateMessage>): void {
throw new Error('Method not implemented.');
}
public step(deltaTimeInMilliseconds: number): void {} public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void { public draw(renderer: Renderer): void {

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { Drawable, Renderer } from 'sdf-2d'; import { Drawable, Renderer } from 'sdf-2d';
import { CommandExecutors, Id, Random, PlanetBase } from 'shared'; import { CommandExecutors, Id, Random, PlanetBase, UpdateMessage } from 'shared';
import { RenderCommand } from '../commands/types/render'; import { RenderCommand } from '../commands/types/render';
import { Polygon } from '../shapes/polygon'; import { Polygon } from '../shapes/polygon';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
@ -18,6 +18,10 @@ export class PlanetView extends PlanetBase implements ViewObject {
(this.shape as any).randomOffset = Random.getRandom(); (this.shape as any).randomOffset = Random.getRandom();
} }
public update(message: Array<UpdateMessage>): void {
throw new Error('Method not implemented.');
}
public step(deltaTimeInMilliseconds: number): void { public step(deltaTimeInMilliseconds: number): void {
(this.shape as any).randomOffset += deltaTimeInMilliseconds / 4000; (this.shape as any).randomOffset += deltaTimeInMilliseconds / 4000;
} }

View file

@ -0,0 +1,37 @@
import { vec2 } from 'gl-matrix';
import { Renderer } from 'sdf-2d';
import { Circle, Id, PlayerCharacterBase, UpdateMessage } from 'shared';
import { BlobShape } from '../shapes/blob-shape';
import { ViewObject } from './view-object';
export class PlayerCharacterView extends PlayerCharacterBase implements ViewObject {
private shape: BlobShape;
constructor(
id: Id,
name: string,
colorIndex: number,
head?: Circle,
leftFoot?: Circle,
rightFoot?: Circle,
) {
super(id, name, colorIndex, head, leftFoot, rightFoot);
this.shape = new BlobShape(colorIndex);
}
public update(updates: Array<UpdateMessage>) {
updates.forEach((u) => ((this as any)[u.key] = u.value));
}
public get position(): vec2 {
return this.head!.center;
}
public step(deltaTimeInMilliseconds: number): void {}
public draw(renderer: Renderer): void {
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
renderer.addDrawable(this.shape);
}
}

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { CircleLight, Renderer } from 'sdf-2d'; import { CircleLight, Renderer } from 'sdf-2d';
import { Id, ProjectileBase, rgb } from 'shared'; import { Id, ProjectileBase, rgb, UpdateMessage } from 'shared';
import { ViewObject } from './view-object'; import { ViewObject } from './view-object';
import { Circle } from '../shapes/circle'; import { Circle } from '../shapes/circle';
@ -14,7 +14,14 @@ export class ProjectileView extends ProjectileBase implements ViewObject {
this.light = new CircleLight(center, rgb(1, 0.5, 0), 0.15); this.light = new CircleLight(center, rgb(1, 0.5, 0), 0.15);
} }
public step(deltaTimeInMilliseconds: number): void {} update(updates: Array<UpdateMessage>): void {
updates.forEach((u) => ((this as any)[u.key] = u.value));
}
public step(deltaTimeInMilliseconds: number): void {
this.circle.center = this.center;
this.light.center = this.center;
}
public draw(renderer: Renderer): void { public draw(renderer: Renderer): void {
renderer.addDrawable(this.circle); renderer.addDrawable(this.circle);

View file

@ -1,7 +1,8 @@
import { Renderer } from 'sdf-2d'; import { Renderer } from 'sdf-2d';
import { GameObject } from 'shared'; import { GameObject, UpdateMessage } from 'shared';
export interface ViewObject extends GameObject { export interface ViewObject extends GameObject {
update(updates: Array<UpdateMessage>): void;
step(deltaTimeInMilliseconds: number): void; step(deltaTimeInMilliseconds: number): void;
draw(renderer: Renderer): void; draw(renderer: Renderer): void;
} }

View file

@ -49,9 +49,10 @@ canvas {
body { body {
#landing-ui { #landing-ui {
position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute;
top: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -60,15 +61,13 @@ body {
} }
#overlay { #overlay {
margin: 0.5rem 1.25rem; height: 100%;
width: 100%;
position: absolute; position: absolute;
right: 0; top: 0;
font-size: 0.75rem;
white-space: pre;
font-family: 'Lucida Console', Monaco, monospace;
@media (max-width: $breakpoint) { pointer-events: none;
font-size: 0.6rem;
} font-size: 0.75rem;
} }
} }

View file

@ -1,14 +1,15 @@
import { GameObject } from '../../objects/game-object'; import { vec2 } from 'gl-matrix';
import { UpdateObjectMessage } from '../../objects/update-object-message';
import { serializable } from '../../transport/serialization/serializable'; import { serializable } from '../../transport/serialization/serializable';
import { Command } from '../command'; import { Command } from '../command';
@serializable @serializable
export class UpdateObjectsCommand extends Command { export class UpdateObjectsCommand extends Command {
public constructor(public readonly objects: Array<GameObject>) { public constructor(public readonly updates: Array<UpdateObjectMessage>) {
super(); super();
} }
public toArray(): Array<any> { public toArray(): Array<any> {
return [this.objects]; return [this.updates];
} }
} }

View file

@ -2,10 +2,10 @@ export * from './commands/command';
export * from './commands/types/create-objects'; export * from './commands/types/create-objects';
export * from './commands/types/create-player'; export * from './commands/types/create-player';
export * from './commands/types/delete-objects'; export * from './commands/types/delete-objects';
export * from './commands/types/update-objects';
export * from './commands/types/ternary-action'; export * from './commands/types/ternary-action';
export * from './commands/types/move-action'; export * from './commands/types/move-action';
export * from './commands/types/primary-action'; export * from './commands/types/primary-action';
export * from './commands/types/update-objects';
export * from './commands/types/secondary-action'; export * from './commands/types/secondary-action';
export * from './commands/command-receiver'; export * from './commands/command-receiver';
export * from './commands/command-executors'; export * from './commands/command-executors';
@ -36,9 +36,13 @@ export * from './transport/serialization/serializes-to';
export * from './transport/serialization/serializable'; export * from './transport/serialization/serializable';
export * from './transport/serialization/override-deserialization'; export * from './transport/serialization/override-deserialization';
export * from './objects/types/character-base'; export * from './objects/types/character-base';
export * from './objects/update-message';
export * from './objects/types/player-character-base';
export * from './objects/types/lamp-base'; export * from './objects/types/lamp-base';
export * from './objects/types/planet-base'; export * from './objects/types/planet-base';
export * from './objects/types/projectile-base'; export * from './objects/types/projectile-base';
export * from './objects/update-message';
export * from './objects/update-object-message';
export * from './settings'; export * from './settings';
export * from './transport/transport-events'; export * from './transport/transport-events';
export * from './transport/identity'; export * from './transport/identity';

View file

@ -0,0 +1,5 @@
export enum InterpolationType {
flat = 'flat',
linear = 'linear',
linearRotation = 'linearRotation',
}

View file

@ -0,0 +1,23 @@
import { CharacterBase } from './character-base';
import { Circle } from '../../helper/circle';
import { Id } from '../../transport/identity';
import { serializable } from '../../transport/serialization/serializable';
@serializable
export class PlayerCharacterBase extends CharacterBase {
constructor(
id: Id,
public name: string,
colorIndex: number,
head?: Circle,
leftFoot?: Circle,
rightFoot?: Circle,
) {
super(id, colorIndex, head, leftFoot, rightFoot);
}
public toArray(): Array<any> {
const { id, name, colorIndex, head, leftFoot, rightFoot } = this;
return [id, name, colorIndex, head, leftFoot, rightFoot];
}
}

View file

@ -0,0 +1,15 @@
import { serializable } from '../transport/serialization/serializable';
import { InterpolationType } from './interpolation-type';
@serializable
export class UpdateMessage {
constructor(
public key: string,
public value: any,
public interpolationType?: InterpolationType,
) {}
public toArray(): Array<any> {
return [this.key, this.value, this.interpolationType];
}
}

View file

@ -0,0 +1,12 @@
import { Id } from '../main';
import { serializable } from '../transport/serialization/serializable';
import { UpdateMessage } from './update-message';
@serializable
export class UpdateObjectMessage {
constructor(public id: Id, public updates: Array<UpdateMessage>) {}
public toArray(): Array<any> {
return [this.id, this.updates];
}
}

View file

@ -13,6 +13,5 @@
"module": "commonjs", "module": "commonjs",
"composite": true, "composite": true,
"lib": ["dom", "es2017"] "lib": ["dom", "es2017"]
}, }
"include": ["src/**/*.ts"]
} }