diff options
Diffstat (limited to 'client')
| -rw-r--r-- | client/src/RemotePlayerEngine.ts | 76 | ||||
| -rw-r--r-- | client/src/RemotePlayerSprite.ts | 137 | ||||
| -rw-r--r-- | client/src/client.ts | 32 | ||||
| -rw-r--r-- | client/src/events.ts | 12 | ||||
| -rw-r--r-- | client/src/scene.ts | 22 | ||||
| -rw-r--r-- | client/src/socket.ts | 8 | ||||
| -rw-r--r-- | client/src/sprite.ts | 8 | ||||
| -rw-r--r-- | client/src/uiScene.ts | 17 |
8 files changed, 273 insertions, 39 deletions
diff --git a/client/src/RemotePlayerEngine.ts b/client/src/RemotePlayerEngine.ts index f19ebe0..5d85f24 100644 --- a/client/src/RemotePlayerEngine.ts +++ b/client/src/RemotePlayerEngine.ts @@ -1,14 +1,84 @@ import Phaser from 'phaser'; +import type AkkamonStartScene from './scene'; import { akkamonClient } from './app'; +import type { Direction } from './Direction'; +import { + Queue, + RemotePlayerSprite +} from './RemotePlayerSprite'; +import type { + RemoteMovementQueues +} from './events'; export class RemotePlayerEngine { - private scene: Phaser.Scene + private scene: AkkamonStartScene; - constructor(scene: Phaser.Scene) { + private trainerIdToRemotePlayerSprite: Map<string, RemotePlayerSprite> = new Map(); + + constructor(scene: AkkamonStartScene) { this.scene = scene; } - update() { + push(remoteMovementQueues: RemoteMovementQueues) { + this.updateMembers(remoteMovementQueues); + this.pushMovesToSprites(remoteMovementQueues); + } + + pushMovesToSprites(remoteMovementQueues: RemoteMovementQueues) { + this.trainerIdToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { + remoteSprite.push(remoteMovementQueues[key].value); + }); + } + + update(delta: number): void { + this.trainerIdToRemotePlayerSprite.forEach((remoteSprite: RemotePlayerSprite, key: string) => { + if (remoteSprite.isMoving()) { + console.log("remote player currently walking"); + remoteSprite.updatePixelPosition(delta); + } else if (remoteSprite.hasMovesLeft()) { + console.log("remote player starts moving"); + remoteSprite.startMoving(); + } + }); + } + + updateMembers(newRemoteMovementQueues: RemoteMovementQueues) { + const traineridToQueueValue = newRemoteMovementQueues; + + Object.keys(newRemoteMovementQueues).forEach((key: string) => { + + var moveQueue = traineridToQueueValue[key].value; + if (moveQueue !== undefined) { + + // console.log("-> key: " + key + " has position " + newTilePos.x + ", " + newTilePos.y); + + if (!this.trainerIdToRemotePlayerSprite.has(key)) { + // console.log("adding remote player sprite for " + key); + this.trainerIdToRemotePlayerSprite.set(key, + new RemotePlayerSprite({ + scene: this.scene, + tilePos: new Phaser.Math.Vector2(this.scene.spawnPointTilePos!), + texture: this.scene.textures.get("atlas"), + frame: "misa-front", + moveQueue: new Queue(moveQueue) + } + )); + } else { + // console.log("key: " + key + " already had a sprite!"); + } + } + + }); + + this.trainerIdToRemotePlayerSprite.forEach((value: RemotePlayerSprite, key: string) => { + if (!(key in newRemoteMovementQueues)) { + // console.log("removing remote player sprite for " + key); + this.trainerIdToRemotePlayerSprite.get(key)!.destroy(); + this.trainerIdToRemotePlayerSprite.delete(key); + } else { + // console.log("Player " + key + " was not removed!"); + } + }); } } diff --git a/client/src/RemotePlayerSprite.ts b/client/src/RemotePlayerSprite.ts index 8485803..6103d09 100644 --- a/client/src/RemotePlayerSprite.ts +++ b/client/src/RemotePlayerSprite.ts @@ -1 +1,138 @@ import Phaser from 'phaser'; +import AkkamonStartScene from './scene'; +import { PlayerSprite } from './sprite'; +import { GridPhysics } from './GridPhysics'; +import { Direction } from './Direction'; + +export class Queue<T> { + private _data = new Array(); + + constructor(data?: Array<T>) { + if (data !== undefined) { + this._data = data; + } + } + + push(element: T): void { + this._data.push(element); + } + + pushArray(arr: T[]): void { + for (var element of arr) { + this._data.push(element); + } + } + + pop(): T | undefined { + return this._data.shift(); + } + + isEmpty(): boolean { + return this._data.length == 0; + } + + peek() { + return this._data[0]; + } +} + +type RemotePlayerSpriteConfig = { + scene: Phaser.Scene, + tilePos: Phaser.Math.Vector2, + texture: Phaser.Textures.Texture | string, + frame?: string, + moveQueue: Queue<Direction> +} + +export class RemotePlayerSprite extends PlayerSprite { + + private lastTilePos?: Phaser.Math.Vector2; + private moveQueue: Queue<Direction> = new Queue(); + + private movementDirection: Direction = Direction.NONE; + + private speedPixelsPerSecond: number = AkkamonStartScene.TILE_SIZE * 4; + + private tileSizePixelsWalked: number = 0; + + constructor(config: RemotePlayerSpriteConfig) { + super(config); + } + + push(moveQueue: Array<Direction>): void { + for (var direction of moveQueue) { + if (direction !== Direction.NONE) { + this.moveQueue.push(direction); + } + } + // console.log(this.moveQueue); + } + + updatePixelPosition(delta: number): void { + const pixelsToWalkThisUpdate = this.getPixelsToWalk(delta); + + if (!this.willCrossTileBorderThisUpdate(pixelsToWalkThisUpdate)) { + this.move(pixelsToWalkThisUpdate); + } else if (this.shouldContinueMoving()) { + this.move(pixelsToWalkThisUpdate); + } else { + this.move(AkkamonStartScene.TILE_SIZE - this.tileSizePixelsWalked); + this.stopMoving(); + } + } + + shouldContinueMoving(): boolean { + if (this.moveQueue.peek() == this.movementDirection) { + console.log("continueing to move."); + this.moveQueue.pop(); + return true; + } + return false; + } + + willCrossTileBorderThisUpdate(pixelsToWalkThisUpdate: number): boolean { + return (this.tileSizePixelsWalked + pixelsToWalkThisUpdate) >= AkkamonStartScene.TILE_SIZE; + } + + move(pixelsToMove: number): void { + this.tileSizePixelsWalked += pixelsToMove; + this.tileSizePixelsWalked %= AkkamonStartScene.TILE_SIZE; + + const directionVec = GridPhysics.movementDirectionVectors[this.movementDirection]!.clone(); + + const moveVec = directionVec.multiply( + new Phaser.Math.Vector2(pixelsToMove) + ); + + const newPosition = this.getPosition().add(moveVec); + this.newPosition(newPosition); + } + + getPixelsToWalk(delta: number): number { + const deltaInSeconds = delta / 1000; + return this.speedPixelsPerSecond * deltaInSeconds; + } + + hasMovesLeft(): boolean { + return !this.moveQueue.isEmpty(); + } + + isMoving(): boolean { + return this.movementDirection !== Direction.NONE; + } + + startMoving(): void { + if (!this.moveQueue.isEmpty()) { + this.movementDirection = this.moveQueue.pop()!; + this.startAnimation(this.movementDirection); + // console.log("remote player now walking in direction: " + this.movementDirection); + } else { + // console.log("moveQueue empty!"); + } + } + + stopMoving(): void { + this.stopAnimation(this.movementDirection); + this.movementDirection = Direction.NONE; + } +} diff --git a/client/src/client.ts b/client/src/client.ts index 77d6676..f166c88 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -1,10 +1,13 @@ import type AkkamonSession from './session'; -import type { GameState } from './GameState'; import { Socket } from './socket'; +import type { GridPhysics } from './GridPhysics'; +import type { RemotePlayerEngine } from './RemotePlayerEngine'; + import { EventType, HeartBeatReplyEvent, - AkkamonEvent + IncomingEvent, + AkkamonEvent, } from './events'; @@ -12,7 +15,8 @@ export class Client { private session: AkkamonSession; - private akkamonState?: GameState; + private gridPhysics?: GridPhysics; + private remotePlayerEngine?: RemotePlayerEngine; constructor( url: string @@ -20,18 +24,21 @@ export class Client this.session = new Socket(url, this); } - getMutableState(): GameState { - return this.akkamonState!; - } - in(eventString: string) { - let event: AkkamonEvent = JSON.parse(eventString); - console.log("-> client is handling incoming event:"); - console.log(event); + let event: IncomingEvent = JSON.parse(eventString); switch (event.type) { case EventType.HEART_BEAT: + if (this.remotePlayerEngine !== undefined) { + this.remotePlayerEngine.push(event.remoteMovementQueues!); + } this.send(new HeartBeatReplyEvent()); break; + case EventType.PLAYERS_NEARBY: + this.ui.setPlayersNearby(); + break; + default: + console.log("ignored incoming event, doesn't match EventType interface."); + break; } } @@ -42,4 +49,9 @@ export class Client this.session.send(JSON.stringify(event)); } } + + setRemotePlayerEngine(engine: RemotePlayerEngine) { + this.remotePlayerEngine = engine; + } + } diff --git a/client/src/events.ts b/client/src/events.ts index cc001f7..1954fd7 100644 --- a/client/src/events.ts +++ b/client/src/events.ts @@ -1,6 +1,4 @@ import Phaser from 'phaser'; -import type { Player } from './player'; -import type { GameState } from './GameState'; import type { Direction } from './Direction'; export enum EventType { @@ -15,6 +13,16 @@ export interface AkkamonEvent { type: EventType } +export type TrainerPosition = { x: number, y: number } + +export type RemoteMovementQueues = { + [trainerId: string]: { value: Array<Direction> } +} + +export interface IncomingEvent extends AkkamonEvent { + remoteMovementQueues?: RemoteMovementQueues +} + export class PlayerRegistrationEvent implements AkkamonEvent { public type: EventType = EventType.PLAYER_REGISTRATION; diff --git a/client/src/scene.ts b/client/src/scene.ts index 52edbdd..b6924d4 100644 --- a/client/src/scene.ts +++ b/client/src/scene.ts @@ -1,6 +1,4 @@ import Phaser from 'phaser'; -import { akkamonClient } from './app'; -import { Player } from './player'; import { PlayerSprite } from './sprite'; import { GridControls } from './GridControls'; @@ -9,10 +7,7 @@ import { Direction } from './Direction'; import { RemotePlayerEngine } from './RemotePlayerEngine'; - -type RemotePlayerStates = { - [name: string]: Player -} +import { akkamonClient } from './app'; export default class AkkamonStartScene extends Phaser.Scene { @@ -24,6 +19,11 @@ export default class AkkamonStartScene extends Phaser.Scene private remotePlayerEngine?: RemotePlayerEngine + public spawnPointTilePos?: { + x: number, + y: number + }; + directionToAnimation: { [key in Direction]: string } = { @@ -34,7 +34,6 @@ export default class AkkamonStartScene extends Phaser.Scene [Direction.NONE]: "misa-front-walk" } - remotePlayerSprites: {[name: string]: PlayerSprite} = {}; spawnPoint: Phaser.Types.Tilemaps.TiledObject | undefined; @@ -88,19 +87,15 @@ export default class AkkamonStartScene extends Phaser.Scene Math.floor(this.spawnPoint.x! / AkkamonStartScene.TILE_SIZE), Math.floor(this.spawnPoint.y! / AkkamonStartScene.TILE_SIZE), ); + this.spawnPointTilePos = tilePos; let player = new PlayerSprite({ scene: this, tilePos: tilePos, texture: this.textures.get("atlas"), frame: "misa-front", - player: new Player({ - trainerId: 'ash', - position: tilePos - })// this.akkamonState.getLocalMutablePlayerState(), }); - this.add.existing(player); this.gridPhysics = new GridPhysics(player, map); this.gridControls = new GridControls( this.input, @@ -108,6 +103,7 @@ export default class AkkamonStartScene extends Phaser.Scene ); this.remotePlayerEngine = new RemotePlayerEngine(this); + akkamonClient.setRemotePlayerEngine(this.remotePlayerEngine); this.createPlayerAnimation(Direction.LEFT, 0, 3); this.createPlayerAnimation(Direction.RIGHT, 0, 3); @@ -120,12 +116,14 @@ export default class AkkamonStartScene extends Phaser.Scene camera.roundPixels = true; camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels); + this.scene.launch('AkkamonUI'); } update(time: number, delta: number) { this.gridControls!.update(); this.gridPhysics!.update(delta); + this.remotePlayerEngine!.update(delta); } private createPlayerAnimation(direction: Direction, start: number, end: number) { diff --git a/client/src/socket.ts b/client/src/socket.ts index 8e2b9ef..4e83f89 100644 --- a/client/src/socket.ts +++ b/client/src/socket.ts @@ -14,17 +14,11 @@ export class Socket extends WebSocket implements AkkamonSession super(url); this.onopen = function echo(this: WebSocket, ev: Event) { - console.log("opening socket"); - console.log("this is the websocket"); - console.log(this); - console.log("logging in the session to the server"); + console.log("Sending PlayerRegistrationEvent."); client.send(new PlayerRegistrationEvent()); } this.onmessage = function incomingMessage(this: WebSocket, ev: MessageEvent) { - //console.log("received message from the server!"); - console.log("-> " + ev.data); - // console.log("calling client.in:"); client.in(ev.data); } } diff --git a/client/src/sprite.ts b/client/src/sprite.ts index 960d374..948d8d9 100644 --- a/client/src/sprite.ts +++ b/client/src/sprite.ts @@ -1,6 +1,5 @@ import Phaser from 'phaser'; import AkkamonStartScene from './scene'; -import type { Player } from './player'; import type { Direction } from './Direction'; type PlayerSpriteConfig = { @@ -8,16 +7,13 @@ type PlayerSpriteConfig = { tilePos: Phaser.Math.Vector2, texture: Phaser.Textures.Texture | string, frame?: string, - player: Player, } interface AkkamonPlayerSprite extends Phaser.GameObjects.Sprite { - player: Player } export class PlayerSprite extends Phaser.GameObjects.Sprite implements AkkamonPlayerSprite { - player: Player; tilePos: Phaser.Math.Vector2; constructor(config: PlayerSpriteConfig) { @@ -30,10 +26,12 @@ export class PlayerSprite extends Phaser.GameObjects.Sprite implements AkkamonPl config.texture, config.frame); - this.player = config.player; this.tilePos = new Phaser.Math.Vector2(config.tilePos.x, config.tilePos.y); this.setOrigin(0.5, 1); + + // add to scene! + config.scene.add.existing(this); } getPosition(): Phaser.Math.Vector2 { diff --git a/client/src/uiScene.ts b/client/src/uiScene.ts new file mode 100644 index 0000000..8b92954 --- /dev/null +++ b/client/src/uiScene.ts @@ -0,0 +1,17 @@ + +export class AkkamonUI extends Phaser.Scene +{ + constructor () + { + super('AkkamonUI'); + } + + preload() { + + } + + create () { + + } + +} |
