import CanvasTexture = Phaser.Textures.CanvasTexture;
import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationNames} from "../Player/Animation";
+import {PlayerMovement} from "./PlayerMovement";
+import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
export enum Textures {
Player = "male1"
startY = 32; // 1 case
circleTexture: CanvasTexture;
initPosition: PositionInterface;
+ private playersPositionInterpolator = new PlayersPositionInterpolator();
MapKey: string;
MapUrlFile: string;
update(time: number, delta: number) : void {
this.currentTick = time;
this.CurrentPlayer.moveUser(delta);
+
+ // Let's move all users
+ let updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
+ updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => {
+ let player : GamerInterface | undefined = this.MapPlayersByKey.get(userId);
+ if (player === undefined) {
+ throw new Error('Cannot find player with ID "' + userId +'"');
+ }
+ player.updatePosition(moveEvent);
+ });
+
let nextSceneKey = this.checkToExit();
if(nextSceneKey){
this.scene.start(nextSceneKey.key);
});
}
- private findPlayerInMap(UserId : string) : GamerInterface | null{
- return this.MapPlayersByKey.get(UserId);
- /*let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId);
- if(!player){
- return null;
- }
- return (player as GamerInterface);*/
- }
-
/**
* Create new player
*/
player.destroy();
this.MapPlayers.remove(player);
this.MapPlayersByKey.delete(userId);
+ this.playersPositionInterpolator.removePlayer(userId);
}
updatePlayerPosition(message: MessageUserMovedInterface): void {
if (player === undefined) {
throw new Error('Cannot find player with ID "' + message.userId +'"');
}
- player.updatePosition(message.position);
+
+ // We do not update the player position directly (because it is sent only every 200ms).
+ // Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms.
+ let playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY);
+ this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
}
shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
import {HasMovedEvent} from "./GameManager";
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
+import {PositionInterface} from "../../Connection";
export class PlayerMovement {
- public constructor(private startPosition: HasMovedEvent, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
+ public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
}
public isOutdated(tick: number): boolean {
+ //console.log(tick, this.endTick, MAX_EXTRAPOLATION_TIME)
+
+ // If the endPosition is NOT moving, no extrapolation needed.
+ if (this.endPosition.moving === false && tick > this.endTick) {
+ return true;
+ }
+
return tick > this.endTick + MAX_EXTRAPOLATION_TIME;
}
public getPosition(tick: number): HasMovedEvent {
+ // Special case: end position reached and end position is not moving
+ if (tick >= this.endTick && this.endPosition.moving === false) {
+ return this.endPosition;
+ }
+
let x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x;
let y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y;
x,
y,
direction: this.endPosition.direction,
- moving: this.endPosition.moving
+ moving: true
}
}
}
* This class is in charge of computing the position of all players.
* Player movement is delayed by 200ms so position depends on ticks.
*/
+import {PlayerMovement} from "./PlayerMovement";
+import {HasMovedEvent} from "./GameManager";
+
export class PlayersPositionInterpolator {
+ playerMovements: Map<string, PlayerMovement> = new Map<string, PlayerMovement>();
+
+ updatePlayerPosition(userId: string, playerMovement: PlayerMovement) : void {
+ this.playerMovements.set(userId, playerMovement);
+ }
+
+ removePlayer(userId: string): void {
+ this.playerMovements.delete(userId);
+ }
+ getUpdatedPositions(tick: number) : Map<string, HasMovedEvent> {
+ let positions = new Map<string, HasMovedEvent>();
+ this.playerMovements.forEach((playerMovement: PlayerMovement, userId: string) => {
+ if (playerMovement.isOutdated(tick)) {
+ //console.log("outdated")
+ this.playerMovements.delete(userId);
+ }
+ //console.log("moving")
+ positions.set(userId, playerMovement.getPosition(tick))
+ });
+ return positions;
+ }
}
describe("Interpolation / Extrapolation", () => {
it("should interpolate", () => {
let playerMovement = new PlayerMovement({
- x: 100, y: 200, moving: true, direction: "right"
+ x: 100, y: 200
}, 42000,
{
x: 200, y: 100, moving: true, direction: "up"
moving: true
});
});
+
+ it("should not extrapolate if we stop", () => {
+ let playerMovement = new PlayerMovement({
+ x: 100, y: 200
+ }, 42000,
+ {
+ x: 200, y: 100, moving: false, direction: "up"
+ },
+ 42200
+ );
+
+ expect(playerMovement.getPosition(42300)).toEqual({
+ x: 200,
+ y: 100,
+ direction: 'up',
+ moving: false
+ });
+ });
+
+ it("should should keep moving until it stops", () => {
+ let playerMovement = new PlayerMovement({
+ x: 100, y: 200
+ }, 42000,
+ {
+ x: 200, y: 100, moving: false, direction: "up"
+ },
+ 42200
+ );
+
+ expect(playerMovement.getPosition(42100)).toEqual({
+ x: 150,
+ y: 150,
+ direction: 'up',
+ moving: true
+ });
+ });
})