From e83c58e1a5ce835514d13abad88afe1c0517a6ad Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Sat, 17 Oct 2020 18:35:09 +0200 Subject: [PATCH] Add better object updates --- backend/src/game-server.ts | 12 +++- backend/src/objects/circle-physical.ts | 6 +- ...ysical.ts => player-character-physical.ts} | 64 +++++++++++-------- backend/src/objects/projectile-physical.ts | 7 +- .../physics/containers/bounding-box-list.ts | 2 +- .../physics/containers/bounding-box-tree.ts | 2 +- .../src/physics/physicals/dynamic-physical.ts | 3 + backend/src/players/player.ts | 57 ++++++++++------- backend/src/update-game-object-message.ts | 11 ++++ frontend/src/index.html | 5 +- frontend/src/index.ts | 46 ++++++------- frontend/src/scripts/game.ts | 16 +++-- .../src/scripts/landing-page-background.ts | 3 +- frontend/src/scripts/objects/camera.ts | 6 +- .../src/scripts/objects/character-view.ts | 6 +- .../scripts/objects/game-object-container.ts | 15 ++--- frontend/src/scripts/objects/lamp-view.ts | 6 +- frontend/src/scripts/objects/planet-view.ts | 6 +- .../scripts/objects/player-character-view.ts | 37 +++++++++++ .../src/scripts/objects/projectile-view.ts | 11 +++- frontend/src/scripts/objects/view-object.ts | 3 +- frontend/src/styles/main.scss | 17 +++-- shared/src/commands/types/update-objects.ts | 7 +- shared/src/main.ts | 6 +- shared/src/objects/interpolation-type.ts | 5 ++ .../objects/types/player-character-base.ts | 23 +++++++ shared/src/objects/update-message.ts | 15 +++++ shared/src/objects/update-object-message.ts | 12 ++++ shared/tsconfig.json | 3 +- 29 files changed, 289 insertions(+), 123 deletions(-) rename backend/src/objects/{character-physical.ts => player-character-physical.ts} (82%) create mode 100644 backend/src/update-game-object-message.ts create mode 100644 frontend/src/scripts/objects/player-character-view.ts create mode 100644 shared/src/objects/interpolation-type.ts create mode 100644 shared/src/objects/types/player-character-base.ts create mode 100644 shared/src/objects/update-message.ts create mode 100644 shared/src/objects/update-object-message.ts diff --git a/backend/src/game-server.ts b/backend/src/game-server.ts index 74c134a..2c8403d 100644 --- a/backend/src/game-server.ts +++ b/backend/src/game-server.ts @@ -1,7 +1,13 @@ import { PhysicalContainer } from './physics/containers/physical-container'; import { Player } from './players/player'; 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 { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { Options } from './options'; @@ -22,8 +28,8 @@ export class GameServer { this.objects.initialize(); io.on('connection', (socket: SocketIO.Socket) => { - socket.on(TransportEvents.PlayerJoining, () => { - const player = new Player(this.objects, socket); + socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => { + const player = new Player(playerInfo, this.objects, socket); this.players.push(player); socket.on(TransportEvents.PlayerToServer, (json: string) => { const command = deserialize(json); diff --git a/backend/src/objects/circle-physical.ts b/backend/src/objects/circle-physical.ts index daf6191..753167f 100644 --- a/backend/src/objects/circle-physical.ts +++ b/backend/src/objects/circle-physical.ts @@ -1,5 +1,5 @@ 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 { BoundingBox } from '../physics/bounding-boxes/bounding-box'; import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; @@ -30,6 +30,10 @@ export class CirclePhysical implements Circle, DynamicPhysical, ReactsToCollisio this.recalculateBoundingBox(); } + public calculateUpdates(): UpdateObjectMessage | null { + return null; + } + public get boundingBox(): BoundingBoxBase { return this._boundingBox; } diff --git a/backend/src/objects/character-physical.ts b/backend/src/objects/player-character-physical.ts similarity index 82% rename from backend/src/objects/character-physical.ts rename to backend/src/objects/player-character-physical.ts index a7c8711..9e50b30 100644 --- a/backend/src/objects/character-physical.ts +++ b/backend/src/objects/player-character-physical.ts @@ -1,7 +1,6 @@ import { vec2 } from 'gl-matrix'; import { id, - CharacterBase, settings, MoveActionCommand, serializesTo, @@ -9,6 +8,7 @@ import { last, GameObject, Circle, + PlayerCharacterBase, } from 'shared'; import { DynamicPhysical } from '../physics/physicals/dynamic-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 { PlanetPhysical } from './planet-physical'; 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) -export class CharacterPhysical - extends CharacterBase +@serializesTo(PlayerCharacterBase) +export class PlayerCharacterPhysical + extends PlayerCharacterBase implements DynamicPhysical, ReactsToCollision { public readonly canCollide = true; public readonly canMove = true; @@ -40,32 +42,32 @@ export class CharacterPhysical vec2.create(), vec2.add( vec2.create(), - CharacterPhysical.desiredHeadOffset, - CharacterPhysical.desiredLeftFootOffset, + PlayerCharacterPhysical.desiredHeadOffset, + PlayerCharacterPhysical.desiredLeftFootOffset, ), - CharacterPhysical.desiredRightFootOffset, + PlayerCharacterPhysical.desiredRightFootOffset, ), 1 / 3, ); private static readonly headOffset = vec2.subtract( vec2.create(), - CharacterPhysical.desiredHeadOffset, - CharacterPhysical.centerOfMass, + PlayerCharacterPhysical.desiredHeadOffset, + PlayerCharacterPhysical.centerOfMass, ); private static readonly leftFootOffset = vec2.subtract( vec2.create(), - CharacterPhysical.desiredLeftFootOffset, - CharacterPhysical.centerOfMass, + PlayerCharacterPhysical.desiredLeftFootOffset, + PlayerCharacterPhysical.centerOfMass, ); private static readonly rightFootOffset = vec2.subtract( vec2.create(), - CharacterPhysical.desiredRightFootOffset, - CharacterPhysical.centerOfMass, + PlayerCharacterPhysical.desiredRightFootOffset, + PlayerCharacterPhysical.centerOfMass, ); public static readonly boundRadius = - (CharacterPhysical.headRadius + CharacterPhysical.feetRadius * 2) * 2; + (PlayerCharacterPhysical.headRadius + PlayerCharacterPhysical.feetRadius * 2) * 2; private isDestroyed = false; private direction = 0; @@ -78,6 +80,10 @@ export class CharacterPhysical public rightFoot: CirclePhysical; public bound: CirclePhysical; + public get isAlive(): boolean { + return !this.isDestroyed; + } + private movementActions: Array = []; private lastMovementAction: MoveActionCommand = new MoveActionCommand( vec2.create(), @@ -85,26 +91,28 @@ export class CharacterPhysical ); constructor( + name: string, public readonly colorIndex: number, private readonly container: PhysicalContainer, startPosition: vec2, ) { - super(id(), colorIndex); + super(id(), name, colorIndex); + this.head = new CirclePhysical( - vec2.add(vec2.create(), startPosition, CharacterPhysical.headOffset), - CharacterPhysical.headRadius, + vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.headOffset), + PlayerCharacterPhysical.headRadius, this, container, ); this.leftFoot = new CirclePhysical( - vec2.add(vec2.create(), startPosition, CharacterPhysical.leftFootOffset), - CharacterPhysical.feetRadius, + vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.leftFootOffset), + PlayerCharacterPhysical.feetRadius, this, container, ); this.rightFoot = new CirclePhysical( - vec2.add(vec2.create(), startPosition, CharacterPhysical.rightFootOffset), - CharacterPhysical.feetRadius, + vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.rightFootOffset), + PlayerCharacterPhysical.feetRadius, this, container, ); @@ -114,12 +122,16 @@ export class CharacterPhysical this.bound = new CirclePhysical( vec2.create(), - CharacterPhysical.boundRadius, + PlayerCharacterPhysical.boundRadius, this, container, ); } + public calculateUpdates(): UpdateObjectMessage { + return new UpdateGameObjectMessage(this, ['head', 'leftFoot', 'rightFoot']); + } + public handleMovementAction(c: MoveActionCommand) { this.movementActions.push(c); } @@ -189,7 +201,7 @@ export class CharacterPhysical getBoundingBoxOfCircle( new Circle( 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); vec2.add(bodyCenter, bodyCenter, this.rightFoot.center); vec2.scale(bodyCenter, bodyCenter, 1 / 3); - this.springMove(this.leftFoot, bodyCenter, CharacterPhysical.leftFootOffset); - this.springMove(this.rightFoot, bodyCenter, CharacterPhysical.rightFootOffset); - this.springMove(this.head, bodyCenter, CharacterPhysical.headOffset); + this.springMove(this.leftFoot, bodyCenter, PlayerCharacterPhysical.leftFootOffset); + this.springMove(this.rightFoot, bodyCenter, PlayerCharacterPhysical.rightFootOffset); + this.springMove(this.head, bodyCenter, PlayerCharacterPhysical.headOffset); } private springMove(object: CirclePhysical, center: vec2, offset: vec2) { diff --git a/backend/src/objects/projectile-physical.ts b/backend/src/objects/projectile-physical.ts index fb64343..336cade 100644 --- a/backend/src/objects/projectile-physical.ts +++ b/backend/src/objects/projectile-physical.ts @@ -6,6 +6,8 @@ import { DynamicPhysical } from '../physics/physicals/dynamic-physical'; import { PhysicalContainer } from '../physics/containers/physical-container'; import { PlanetPhysical } from './planet-physical'; 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) export class ProjectilePhysical @@ -15,6 +17,7 @@ export class ProjectilePhysical public readonly canMove = true; private isDestroyed = false; private bounceCount = 0; + private _boundingBox?: ImmutableBoundingBox; public object: CirclePhysical; @@ -28,7 +31,9 @@ export class ProjectilePhysical 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 { if (!this._boundingBox) { diff --git a/backend/src/physics/containers/bounding-box-list.ts b/backend/src/physics/containers/bounding-box-list.ts index 1a9f963..4311f62 100644 --- a/backend/src/physics/containers/bounding-box-list.ts +++ b/backend/src/physics/containers/bounding-box-list.ts @@ -1,4 +1,4 @@ -import { DynamicPhysical } from '../dynamic-physical'; +import { DynamicPhysical } from '../physicals/dynamic-physical'; import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; export class BoundingBoxList { diff --git a/backend/src/physics/containers/bounding-box-tree.ts b/backend/src/physics/containers/bounding-box-tree.ts index 00b1371..62ca08a 100644 --- a/backend/src/physics/containers/bounding-box-tree.ts +++ b/backend/src/physics/containers/bounding-box-tree.ts @@ -1,5 +1,5 @@ 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 class Node { diff --git a/backend/src/physics/physicals/dynamic-physical.ts b/backend/src/physics/physicals/dynamic-physical.ts index a5e03c1..c5b1cc5 100644 --- a/backend/src/physics/physicals/dynamic-physical.ts +++ b/backend/src/physics/physicals/dynamic-physical.ts @@ -1,6 +1,9 @@ +import { UpdateObjectMessage } from 'shared/lib/src/objects/update-object-message'; import { PhysicalBase } from './physical-base'; export interface DynamicPhysical extends PhysicalBase { readonly canMove: true; step(deltaTimeInMilliseconds: number): void; + + calculateUpdates(): UpdateObjectMessage | null; } diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index faadb80..f245cce 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -14,27 +14,28 @@ import { SecondaryActionCommand, settings, Circle, + PlayerInformation, } from 'shared'; import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds'; -import { CharacterPhysical } from '../objects/character-physical'; import { ProjectilePhysical } from '../objects/projectile-physical'; - import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; 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 { isCircleIntersecting } from '../physics/functions/is-circle-intersecting'; 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 { - private character: CharacterPhysical; + private character: PlayerCharacterPhysical; private aspectRatio: number = 16 / 9; private isActive = true; private timeSinceLastProjectile = 0; - private objectsPreviouslyInViewArea: Array = []; - private objectsInViewArea: Array = []; + private objectsPreviouslyInViewArea: Array = []; + private objectsInViewArea: Array = []; private pingTime?: number; private _latency?: number; @@ -56,7 +57,10 @@ export class Player extends CommandReceiver { [MoveActionCommand.type]: (c: MoveActionCommand) => this.character.handleMovementAction(c), [SecondaryActionCommand.type]: (c: SecondaryActionCommand) => { - if (this.timeSinceLastProjectile < settings.projectileCreationInterval) { + if ( + !this.character.isAlive || + this.timeSinceLastProjectile < settings.projectileCreationInterval + ) { return; } @@ -88,7 +92,7 @@ export class Player extends CommandReceiver { const playerBoundingCircle = new Circle( playerPosition, - CharacterPhysical.boundRadius, + PlayerCharacterPhysical.boundRadius, ); const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle); @@ -97,27 +101,26 @@ export class Player extends CommandReceiver { return playerPosition; } - rotation += Math.PI / 12; - radius += 10; + rotation += Math.PI / 8; + radius += 30; } } constructor( + playerInfo: PlayerInformation, private readonly objects: PhysicalContainer, private readonly socket: SocketIO.Socket, ) { super(); const colorIndex = requestColor(); - this.character = new CharacterPhysical( + this.character = new PlayerCharacterPhysical( + playerInfo.name, colorIndex, objects, this.findEmptyPositionForPlayer(), ); - this.objectsPreviouslyInViewArea.push(this.character); - this.objectsInViewArea.push(this.character); - this.objects.addObject(this.character); socket.emit( @@ -138,6 +141,7 @@ export class Player extends CommandReceiver { this.sendObjects(); this.timeSinceLastProjectile += deltaTime; } + public sendObjects() { const viewArea = calculateViewArea(this.character.center, this.aspectRatio, 1.5); const bb = new BoundingBox(); @@ -161,7 +165,11 @@ export class Player extends CommandReceiver { TransportEvents.ServerToPlayer, serialize( 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, serialize( 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, serialize( - new UpdateObjectsCommand([ - ...new Set( - this.objectsInViewArea.filter((p) => p.canMove).map((p) => p.gameObject), - ), - ]), + new UpdateObjectsCommand( + Array.from(new Set(this.objectsInViewArea)) + .filter((p) => p.canMove) + .map((p) => (p as DynamicPhysical).calculateUpdates()) + .filter((p) => p !== null) as any, + ), ), ); } diff --git a/backend/src/update-game-object-message.ts b/backend/src/update-game-object-message.ts new file mode 100644 index 0000000..181ab30 --- /dev/null +++ b/backend/src/update-game-object-message.ts @@ -0,0 +1,11 @@ +import { GameObject, serializesTo, UpdateMessage, UpdateObjectMessage } from 'shared'; + +@serializesTo(UpdateObjectMessage) +export class UpdateGameObjectMessage extends UpdateObjectMessage { + constructor(object: T, keys: Array) { + super( + object.id, + keys.map((k) => new UpdateMessage(k, object[k])), + ); + } +} diff --git a/frontend/src/index.html b/frontend/src/index.html index 667f31e..36fdd8c 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -15,6 +15,9 @@ + +
+

decla.red

@@ -41,7 +44,5 @@
-
- diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 876a70c..945d38a 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -1,7 +1,12 @@ import { glMatrix } from 'gl-matrix'; -import { CharacterBase, LampBase, overrideDeserialization, PlanetBase } from 'shared'; -import { ProjectileBase } from 'shared/src/objects/types/projectile-base'; - +import { + CharacterBase, + LampBase, + overrideDeserialization, + PlanetBase, + PlayerCharacterBase, + ProjectileBase, +} from 'shared'; import { CharacterView } from './scripts/objects/character-view'; import { LampView } from './scripts/objects/lamp-view'; import { ProjectileView } from './scripts/objects/projectile-view'; @@ -10,43 +15,32 @@ import './styles/main.scss'; import { LandingPageBackground } from './scripts/landing-page-background'; import { JoinFormHandler } from './scripts/join-form-handler'; import { Game } from './scripts/game'; +import { PlayerCharacterView } from './scripts/objects/player-character-view'; glMatrix.setMatrixArrayType(Array); overrideDeserialization(CharacterBase, CharacterView); +overrideDeserialization(PlayerCharacterBase, PlayerCharacterView); overrideDeserialization(PlanetBase, PlanetView); overrideDeserialization(LampBase, LampView); 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 () => { try { const landingUI = document.querySelector('#landing-ui') as HTMLElement; - const background = new LandingPageBackground(); - const joinHandler = new JoinFormHandler( - document.querySelector('#join-game-form') as HTMLFormElement, - document.querySelector('#server-container') as HTMLElement, - ); + const joinGameForm = document.querySelector('#join-game-form') as HTMLFormElement; + const serverContainer = document.querySelector('#server-container') as HTMLElement; + const canvas = document.querySelector('canvas') as HTMLCanvasElement; + const overlay = document.querySelector('#overlay') as HTMLElement; + + const background = new LandingPageBackground(canvas); + const joinHandler = new JoinFormHandler(joinGameForm, serverContainer); + const playerDecision = await joinHandler.getPlayerDecision(); landingUI.style.display = 'none'; - console.log(playerDecision); + background.destroy(); - await new Game(playerDecision).start(); + await new Game(playerDecision, canvas, overlay).start(); } catch (e) { console.error(e); alert(e); diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 2c11171..a6b7b52 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -16,6 +16,7 @@ import { TransportEvents, SetAspectRatioActionCommand, rgb, + PlayerInformation, } from 'shared'; import io from 'socket.io-client'; import { KeyboardListener } from './commands/generators/keyboard-listener'; @@ -32,15 +33,16 @@ import { Polygon } from './shapes/polygon'; export class Game { public readonly gameObjects = new GameObjectContainer(this); - private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement; private renderer!: Renderer; private socket!: SocketIOClient.Socket; private promises: Promise<[void, void]>; private deltaTimeCalculator = new DeltaTimeCalculator(); - private overlay: HTMLElement = document.querySelector('#overlay') as HTMLDivElement; - constructor(playerDecision: PlayerDecision) { - console.log(playerDecision.server); + constructor( + private readonly playerDecision: PlayerDecision, + private readonly canvas: HTMLCanvasElement, + private readonly overlay: HTMLElement, + ) { this.promises = Promise.all([ this.setupCommunication(playerDecision.server), this.setupRenderer(), @@ -66,7 +68,9 @@ export class Game { this.socket.emit(TransportEvents.Pong); }); - this.socket.emit(TransportEvents.PlayerJoining, null); + this.socket.emit(TransportEvents.PlayerJoining, { + name: this.playerDecision.playerName, + } as PlayerInformation); broadcastCommands( [ @@ -98,7 +102,7 @@ export class Game { }, { ...CircleLight.descriptor, - shaderCombinationSteps: [0, 1, 2, 4, 8, 16], + shaderCombinationSteps: [0, 1, 2, 4, 8], }, { ...Flashlight.descriptor, diff --git a/frontend/src/scripts/landing-page-background.ts b/frontend/src/scripts/landing-page-background.ts index 5037fac..fda6efc 100644 --- a/frontend/src/scripts/landing-page-background.ts +++ b/frontend/src/scripts/landing-page-background.ts @@ -18,10 +18,9 @@ const LangindPagePolygon = NoisyPolygonFactory( ); export class LandingPageBackground { - private readonly canvas = document.querySelector('canvas') as HTMLCanvasElement; private renderer!: Renderer; - constructor() { + constructor(private readonly canvas: HTMLCanvasElement) { this.start(); } diff --git a/frontend/src/scripts/objects/camera.ts b/frontend/src/scripts/objects/camera.ts index 437d2b0..1116fc8 100644 --- a/frontend/src/scripts/objects/camera.ts +++ b/frontend/src/scripts/objects/camera.ts @@ -1,6 +1,6 @@ import { vec2 } from 'gl-matrix'; 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 { ViewObject } from './view-object'; @@ -14,6 +14,10 @@ export class Camera extends GameObject implements ViewObject { super(null); } + public update(updates: Array) { + throw new Error(); + } + public step(deltaTimeInMilliseconds: number): void {} public draw(renderer: Renderer) { diff --git a/frontend/src/scripts/objects/character-view.ts b/frontend/src/scripts/objects/character-view.ts index 8f7560e..6f617c7 100644 --- a/frontend/src/scripts/objects/character-view.ts +++ b/frontend/src/scripts/objects/character-view.ts @@ -1,6 +1,6 @@ import { vec2 } from 'gl-matrix'; 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 { ViewObject } from './view-object'; @@ -19,6 +19,10 @@ export class CharacterView extends CharacterBase implements ViewObject { this.shape = new BlobShape(colorIndex); } + public update(updates: Array) { + updates.forEach((u) => ((this as any)[u.key] = u.value)); + } + public get position(): vec2 { return this.head!.center; } diff --git a/frontend/src/scripts/objects/game-object-container.ts b/frontend/src/scripts/objects/game-object-container.ts index 4fcfcbf..2a03745 100644 --- a/frontend/src/scripts/objects/game-object-container.ts +++ b/frontend/src/scripts/objects/game-object-container.ts @@ -5,22 +5,23 @@ import { CreateObjectsCommand, CreatePlayerCommand, DeleteObjectsCommand, + GameObject, Id, UpdateObjectsCommand, } from 'shared'; import { Game } from '../game'; import { Camera } from './camera'; -import { CharacterView } from './character-view'; +import { PlayerCharacterView } from './player-character-view'; import { ViewObject } from './view-object'; export class GameObjectContainer extends CommandReceiver { protected objects: Map = new Map(); - public player!: CharacterView; + public player!: PlayerCharacterView; public camera!: Camera; protected commandExecutors: CommandExecutors = { [CreatePlayerCommand.type]: (c: CreatePlayerCommand) => { - this.player = c.character as CharacterView; + this.player = c.character as PlayerCharacterView; this.camera = new Camera(this.game); this.addObject(this.player); this.addObject(this.camera); @@ -33,13 +34,7 @@ export class GameObjectContainer extends CommandReceiver { c.ids.forEach((id: Id) => this.objects.delete(id)), [UpdateObjectsCommand.type]: (c: UpdateObjectsCommand) => { - c.objects.forEach((o) => { - this.objects.delete(o.id); - this.addObject(o as ViewObject); - if (o.id === this.player.id) { - this.player = o as CharacterView; - } - }); + c.updates.forEach((u) => this.objects.get(u.id)?.update(u.updates)); }, }; diff --git a/frontend/src/scripts/objects/lamp-view.ts b/frontend/src/scripts/objects/lamp-view.ts index ece2595..c4590a7 100644 --- a/frontend/src/scripts/objects/lamp-view.ts +++ b/frontend/src/scripts/objects/lamp-view.ts @@ -1,6 +1,6 @@ import { vec2, vec3 } from 'gl-matrix'; 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 { ViewObject } from './view-object'; @@ -16,6 +16,10 @@ export class LampView extends LampBase implements ViewObject { this.light = new CircleLight(center, color, lightness); } + public update(message: Array): void { + throw new Error('Method not implemented.'); + } + public step(deltaTimeInMilliseconds: number): void {} public draw(renderer: Renderer): void { diff --git a/frontend/src/scripts/objects/planet-view.ts b/frontend/src/scripts/objects/planet-view.ts index 22f1263..4f45f88 100644 --- a/frontend/src/scripts/objects/planet-view.ts +++ b/frontend/src/scripts/objects/planet-view.ts @@ -1,6 +1,6 @@ import { vec2 } from 'gl-matrix'; 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 { Polygon } from '../shapes/polygon'; import { ViewObject } from './view-object'; @@ -18,6 +18,10 @@ export class PlanetView extends PlanetBase implements ViewObject { (this.shape as any).randomOffset = Random.getRandom(); } + public update(message: Array): void { + throw new Error('Method not implemented.'); + } + public step(deltaTimeInMilliseconds: number): void { (this.shape as any).randomOffset += deltaTimeInMilliseconds / 4000; } diff --git a/frontend/src/scripts/objects/player-character-view.ts b/frontend/src/scripts/objects/player-character-view.ts new file mode 100644 index 0000000..8d94cc9 --- /dev/null +++ b/frontend/src/scripts/objects/player-character-view.ts @@ -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) { + 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); + } +} diff --git a/frontend/src/scripts/objects/projectile-view.ts b/frontend/src/scripts/objects/projectile-view.ts index 8968475..86c2047 100644 --- a/frontend/src/scripts/objects/projectile-view.ts +++ b/frontend/src/scripts/objects/projectile-view.ts @@ -1,6 +1,6 @@ import { vec2 } from 'gl-matrix'; 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 { 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); } - public step(deltaTimeInMilliseconds: number): void {} + update(updates: Array): 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 { renderer.addDrawable(this.circle); diff --git a/frontend/src/scripts/objects/view-object.ts b/frontend/src/scripts/objects/view-object.ts index d3ae529..a395011 100644 --- a/frontend/src/scripts/objects/view-object.ts +++ b/frontend/src/scripts/objects/view-object.ts @@ -1,7 +1,8 @@ import { Renderer } from 'sdf-2d'; -import { GameObject } from 'shared'; +import { GameObject, UpdateMessage } from 'shared'; export interface ViewObject extends GameObject { + update(updates: Array): void; step(deltaTimeInMilliseconds: number): void; draw(renderer: Renderer): void; } diff --git a/frontend/src/styles/main.scss b/frontend/src/styles/main.scss index 42b56cf..e0d97fd 100644 --- a/frontend/src/styles/main.scss +++ b/frontend/src/styles/main.scss @@ -49,9 +49,10 @@ canvas { body { #landing-ui { - position: absolute; width: 100%; height: 100%; + position: absolute; + top: 0; display: flex; flex-direction: column; @@ -60,15 +61,13 @@ body { } #overlay { - margin: 0.5rem 1.25rem; + height: 100%; + width: 100%; position: absolute; - right: 0; - font-size: 0.75rem; - white-space: pre; - font-family: 'Lucida Console', Monaco, monospace; + top: 0; - @media (max-width: $breakpoint) { - font-size: 0.6rem; - } + pointer-events: none; + + font-size: 0.75rem; } } diff --git a/shared/src/commands/types/update-objects.ts b/shared/src/commands/types/update-objects.ts index 8469483..8ff9356 100644 --- a/shared/src/commands/types/update-objects.ts +++ b/shared/src/commands/types/update-objects.ts @@ -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 { Command } from '../command'; @serializable export class UpdateObjectsCommand extends Command { - public constructor(public readonly objects: Array) { + public constructor(public readonly updates: Array) { super(); } public toArray(): Array { - return [this.objects]; + return [this.updates]; } } diff --git a/shared/src/main.ts b/shared/src/main.ts index 94325b5..6154297 100644 --- a/shared/src/main.ts +++ b/shared/src/main.ts @@ -2,10 +2,10 @@ export * from './commands/command'; export * from './commands/types/create-objects'; export * from './commands/types/create-player'; export * from './commands/types/delete-objects'; -export * from './commands/types/update-objects'; export * from './commands/types/ternary-action'; export * from './commands/types/move-action'; export * from './commands/types/primary-action'; +export * from './commands/types/update-objects'; export * from './commands/types/secondary-action'; export * from './commands/command-receiver'; export * from './commands/command-executors'; @@ -36,9 +36,13 @@ export * from './transport/serialization/serializes-to'; export * from './transport/serialization/serializable'; export * from './transport/serialization/override-deserialization'; 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/planet-base'; export * from './objects/types/projectile-base'; +export * from './objects/update-message'; +export * from './objects/update-object-message'; export * from './settings'; export * from './transport/transport-events'; export * from './transport/identity'; diff --git a/shared/src/objects/interpolation-type.ts b/shared/src/objects/interpolation-type.ts new file mode 100644 index 0000000..49a8aed --- /dev/null +++ b/shared/src/objects/interpolation-type.ts @@ -0,0 +1,5 @@ +export enum InterpolationType { + flat = 'flat', + linear = 'linear', + linearRotation = 'linearRotation', +} diff --git a/shared/src/objects/types/player-character-base.ts b/shared/src/objects/types/player-character-base.ts new file mode 100644 index 0000000..61d9e62 --- /dev/null +++ b/shared/src/objects/types/player-character-base.ts @@ -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 { + const { id, name, colorIndex, head, leftFoot, rightFoot } = this; + return [id, name, colorIndex, head, leftFoot, rightFoot]; + } +} diff --git a/shared/src/objects/update-message.ts b/shared/src/objects/update-message.ts new file mode 100644 index 0000000..7ebb56c --- /dev/null +++ b/shared/src/objects/update-message.ts @@ -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 { + return [this.key, this.value, this.interpolationType]; + } +} diff --git a/shared/src/objects/update-object-message.ts b/shared/src/objects/update-object-message.ts new file mode 100644 index 0000000..89c57a0 --- /dev/null +++ b/shared/src/objects/update-object-message.ts @@ -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) {} + + public toArray(): Array { + return [this.id, this.updates]; + } +} diff --git a/shared/tsconfig.json b/shared/tsconfig.json index 3a55c4e..f85c232 100644 --- a/shared/tsconfig.json +++ b/shared/tsconfig.json @@ -13,6 +13,5 @@ "module": "commonjs", "composite": true, "lib": ["dom", "es2017"] - }, - "include": ["src/**/*.ts"] + } }