Adding PlayersPositionInterpolator to interpolate/extrapolate players positions
This commit is contained in:
parent
d69ce8a6a6
commit
d72e60610e
@ -13,6 +13,8 @@ import Sprite = Phaser.GameObjects.Sprite;
|
|||||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||||
import {AddPlayerInterface} from "./AddPlayerInterface";
|
import {AddPlayerInterface} from "./AddPlayerInterface";
|
||||||
import {PlayerAnimationNames} from "../Player/Animation";
|
import {PlayerAnimationNames} from "../Player/Animation";
|
||||||
|
import {PlayerMovement} from "./PlayerMovement";
|
||||||
|
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
|
||||||
|
|
||||||
export enum Textures {
|
export enum Textures {
|
||||||
Player = "male1"
|
Player = "male1"
|
||||||
@ -37,6 +39,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
startY = 32; // 1 case
|
startY = 32; // 1 case
|
||||||
circleTexture: CanvasTexture;
|
circleTexture: CanvasTexture;
|
||||||
initPosition: PositionInterface;
|
initPosition: PositionInterface;
|
||||||
|
private playersPositionInterpolator = new PlayersPositionInterpolator();
|
||||||
|
|
||||||
MapKey: string;
|
MapKey: string;
|
||||||
MapUrlFile: string;
|
MapUrlFile: string;
|
||||||
@ -381,6 +384,17 @@ export class GameScene extends Phaser.Scene {
|
|||||||
update(time: number, delta: number) : void {
|
update(time: number, delta: number) : void {
|
||||||
this.currentTick = time;
|
this.currentTick = time;
|
||||||
this.CurrentPlayer.moveUser(delta);
|
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();
|
let nextSceneKey = this.checkToExit();
|
||||||
if(nextSceneKey){
|
if(nextSceneKey){
|
||||||
this.scene.start(nextSceneKey.key);
|
this.scene.start(nextSceneKey.key);
|
||||||
@ -424,15 +438,6 @@ export class GameScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* Create new player
|
||||||
*/
|
*/
|
||||||
@ -475,6 +480,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
player.destroy();
|
player.destroy();
|
||||||
this.MapPlayers.remove(player);
|
this.MapPlayers.remove(player);
|
||||||
this.MapPlayersByKey.delete(userId);
|
this.MapPlayersByKey.delete(userId);
|
||||||
|
this.playersPositionInterpolator.removePlayer(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerPosition(message: MessageUserMovedInterface): void {
|
updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||||
@ -482,7 +488,11 @@ export class GameScene extends Phaser.Scene {
|
|||||||
if (player === undefined) {
|
if (player === undefined) {
|
||||||
throw new Error('Cannot find player with ID "' + message.userId +'"');
|
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) {
|
shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
import {HasMovedEvent} from "./GameManager";
|
import {HasMovedEvent} from "./GameManager";
|
||||||
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
||||||
|
import {PositionInterface} from "../../Connection";
|
||||||
|
|
||||||
export class PlayerMovement {
|
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 {
|
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;
|
return tick > this.endTick + MAX_EXTRAPOLATION_TIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPosition(tick: number): HasMovedEvent {
|
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 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;
|
let y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y;
|
||||||
|
|
||||||
@ -17,7 +30,7 @@ export class PlayerMovement {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
direction: this.endPosition.direction,
|
direction: this.endPosition.direction,
|
||||||
moving: this.endPosition.moving
|
moving: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,30 @@
|
|||||||
* This class is in charge of computing the position of all players.
|
* This class is in charge of computing the position of all players.
|
||||||
* Player movement is delayed by 200ms so position depends on ticks.
|
* Player movement is delayed by 200ms so position depends on ticks.
|
||||||
*/
|
*/
|
||||||
export class PlayersPositionInterpolator {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import {PlayerMovement} from "../../../src/Phaser/Game/PlayerMovement";
|
|||||||
describe("Interpolation / Extrapolation", () => {
|
describe("Interpolation / Extrapolation", () => {
|
||||||
it("should interpolate", () => {
|
it("should interpolate", () => {
|
||||||
let playerMovement = new PlayerMovement({
|
let playerMovement = new PlayerMovement({
|
||||||
x: 100, y: 200, moving: true, direction: "right"
|
x: 100, y: 200
|
||||||
}, 42000,
|
}, 42000,
|
||||||
{
|
{
|
||||||
x: 200, y: 100, moving: true, direction: "up"
|
x: 200, y: 100, moving: true, direction: "up"
|
||||||
@ -37,4 +37,40 @@ describe("Interpolation / Extrapolation", () => {
|
|||||||
moving: true
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user