Adding HdpiManager to start and scale from a default resolution that is correct by default for the game.

Fixing VirtualJoystick on resize.
This commit is contained in:
David Négrier 2021-05-04 11:29:37 +02:00
parent 6a2326c4b3
commit 04d3cf8593
9 changed files with 213 additions and 32 deletions

View File

@ -10,8 +10,8 @@ const TURN_USER: string = process.env.TURN_USER || '';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || ''; const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true"; const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
const RESOLUTION = 1 / window.devicePixelRatio; const RESOLUTION = 2;
const ZOOM_LEVEL = 2 * window.devicePixelRatio/*3/4*/; const ZOOM_LEVEL = 1;
const POSITION_DELAY = 200; // Wait 200ms between sending position events const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;

View File

@ -1,4 +1,6 @@
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
import ScaleManager = Phaser.Scale.ScaleManager;
import {waScaleManager} from "../Services/WaScaleManager";
const outOfScreenX = -1000; const outOfScreenX = -1000;
const outOfScreenY = -1000; const outOfScreenY = -1000;
@ -11,7 +13,8 @@ export const joystickThumbKey = 'joystickThumb';
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png'; export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
export class MobileJoystick extends VirtualJoystick { export class MobileJoystick extends VirtualJoystick {
private resizeCallback: () => void;
constructor(scene: Phaser.Scene) { constructor(scene: Phaser.Scene) {
super(scene, { super(scene, {
x: outOfScreenX, x: outOfScreenX,
@ -31,5 +34,18 @@ export class MobileJoystick extends VirtualJoystick {
this.x = outOfScreenX; this.x = outOfScreenX;
this.y = outOfScreenY; this.y = outOfScreenY;
}); });
this.resizeCallback = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
} }
}
private resize() {
this.base.setDisplaySize(60 / waScaleManager.zoomModifier, 60 / waScaleManager.zoomModifier);
this.thumb.setDisplaySize(30 / waScaleManager.zoomModifier, 30 / waScaleManager.zoomModifier);
this.setRadius(20 / waScaleManager.zoomModifier);
}
public destroy() {
super.destroy();
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
}
}

View File

@ -81,6 +81,7 @@ import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoading
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import {PinchManager} from "../UserInput/PinchManager";
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
import {waScaleManager} from "../Services/WaScaleManager";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface|null,
@ -176,6 +177,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>(); private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined; private originalMapUrl: string|undefined;
private cursorKeys: any; private cursorKeys: any;
private pinchManager: PinchManager|undefined;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({ super({
@ -364,7 +366,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.startLayerName = urlManager.getStartLayerNameFromUrl(); this.startLayerName = urlManager.getStartLayerNameFromUrl();
if (touchScreenManager.supportTouchScreen) { if (touchScreenManager.supportTouchScreen) {
new PinchManager(this); this.pinchManager = new PinchManager(this);
} }
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError()) this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
@ -420,7 +422,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//initialise list of other player //initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true}); this.MapPlayers = this.physics.add.group({immovable: true});
//create input to move //create input to move
mediaManager.setUserInputManager(this.userInputManager); mediaManager.setUserInputManager(this.userInputManager);
this.userInputManager = new UserInputManager(this); this.userInputManager = new UserInputManager(this);
@ -899,6 +901,8 @@ ${escapedMessage}
this.simplePeer.closeAllConnections(); this.simplePeer.closeAllConnections();
this.simplePeer?.unregister(); this.simplePeer?.unregister();
this.messageSubscription?.unsubscribe(); this.messageSubscription?.unsubscribe();
this.userInputManager.destroy();
this.pinchManager?.destroy();
for(const iframeEvents of this.iframeSubscriptionList){ for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe(); iframeEvents.unsubscribe();
@ -1185,11 +1189,17 @@ ${escapedMessage}
*/ */
update(time: number, delta: number) : void { update(time: number, delta: number) : void {
if (this.cursorKeys.up.isDown) { if (this.cursorKeys.up.isDown) {
this.cameras.main.zoom *= 1.2; //this.cameras.main.zoom *= 1.2;
//this.scale.setGameSize(this.scale.width * 1.2, this.scale.height * 1.2);
waScaleManager.zoomModifier *= 1.2;
this.updateCameraOffset();
} else if(this.cursorKeys.down.isDown) { } else if(this.cursorKeys.down.isDown) {
this.cameras.main.zoom *= 0.8; //this.scale.setGameSize(this.scale.width * 0.8, this.scale.height * 0.8);
//this.cameras.main.zoom *= 0.8;
waScaleManager.zoomModifier /= 1.2;
this.updateCameraOffset();
} }
mediaManager.setLastUpdateScene(); mediaManager.setLastUpdateScene();
this.currentTick = time; this.currentTick = time;
this.CurrentPlayer.moveUser(delta); this.CurrentPlayer.moveUser(delta);
@ -1425,17 +1435,18 @@ ${escapedMessage}
} }
/** /**
* Updates the offset of the character compared to the center of the screen according to the layout mananger * Updates the offset of the character compared to the center of the screen according to the layout manager
* (tries to put the character in the center of the reamining space if there is a discussion going on. * (tries to put the character in the center of the remaining space if there is a discussion going on.
*/ */
private updateCameraOffset(): void { private updateCameraOffset(): void {
const array = layoutManager.findBiggestAvailableArray(); const array = layoutManager.findBiggestAvailableArray();
let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart; let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart; let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart;
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
// Let's put this in Game coordinates by applying the zoom level: // Let's put this in Game coordinates by applying the zoom level:
this.cameras.main.setFollowOffset(xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2); this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom);
} }
public onCenterChange(): void { public onCenterChange(): void {

View File

@ -0,0 +1,63 @@
import ScaleManager = Phaser.Scale.ScaleManager;
interface Size {
width: number;
height: number;
}
export class HdpiManager {
private _zoomModifier: number = 1;
public constructor(private minGamePixelsNumber: number) {
}
/**
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
*
* Note: the function is returning the optimal size in "game pixels" in the "game" property,
* but also recommends resizing the "real" pixel screen size of the canvas.
* The proposed new real size is a few pixels bigger than the real size available (if the size is not a multiple of the pixel size) and should overflow.
*
* @param realPixelScreenSize
*/
public getOptimalGameSize(realPixelScreenSize: Size): { game: Size, real: Size } {
const realPixelNumber = realPixelScreenSize.width * realPixelScreenSize.height;
// If the screen has not a definition small enough to match the minimum number of pixels we want to display,
// let's make the canvas the size of the screen (in real pixels)
if (realPixelNumber <= this.minGamePixelsNumber) {
return {
game: realPixelScreenSize,
real: realPixelScreenSize
};
}
let i = 1;
while (true) {
if (realPixelNumber <= this.minGamePixelsNumber * i * i) {
break;
}
i++;
}
return {
game: {
width: Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier),
height: Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier),
},
real: {
width: Math.ceil(realPixelScreenSize.width / (i - 1)) * (i - 1),
height: Math.ceil(realPixelScreenSize.height / (i - 1)) * (i - 1),
}
}
}
public get zoomModifier(): number {
return this._zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this._zoomModifier = zoomModifier;
}
}

View File

@ -0,0 +1,47 @@
import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
class WaScaleManager {
private hdpiManager: HdpiManager;
private scaleManager!: ScaleManager;
public constructor(private minGamePixelsNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber);
}
public setScaleManager(scaleManager: ScaleManager) {
this.scaleManager = scaleManager;
}
public applyNewSize() {
const {width, height} = coWebsiteManager.getGameSize();
let devicePixelRatio = 1;
if (window.devicePixelRatio) {
devicePixelRatio = window.devicePixelRatio;
}
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
this.scaleManager.resize(gameSize.width, gameSize.height);
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
};
public get zoomModifier(): number {
return this.hdpiManager.zoomModifier;
}
public set zoomModifier(zoomModifier: number) {
this.hdpiManager.zoomModifier = zoomModifier;
this.applyNewSize();
}
}
export const waScaleManager = new WaScaleManager(640*480);

View File

@ -1,22 +1,20 @@
import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js"; import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js";
import {waScaleManager} from "../Services/WaScaleManager";
export class PinchManager { export class PinchManager {
private scene: Phaser.Scene; private scene: Phaser.Scene;
private pinch!: any; // eslint-disable-line private pinch!: any; // eslint-disable-line
constructor(scene: Phaser.Scene) { constructor(scene: Phaser.Scene) {
this.scene = scene; this.scene = scene;
this.pinch = new Pinch(scene); this.pinch = new Pinch(scene);
this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line
let newZoom = this.scene.cameras.main.zoom * pinch.scaleFactor; waScaleManager.zoomModifier *= pinch.scaleFactor;
if (newZoom < 0.25) {
newZoom = 0.25;
} else if(newZoom > 2) {
newZoom = 2;
}
this.scene.cameras.main.setZoom(newZoom);
}); });
} }
} destroy() {
this.pinch.removeAllListeners();
}
}

View File

@ -59,7 +59,7 @@ export class UserInputManager {
this.initVirtualJoystick(); this.initVirtualJoystick();
} }
} }
initVirtualJoystick() { initVirtualJoystick() {
this.joystick = new MobileJoystick(this.Scene); this.joystick = new MobileJoystick(this.Scene);
this.joystick.on("update", () => { this.joystick.on("update", () => {
@ -171,4 +171,8 @@ export class UserInputManager {
removeSpaceEventListner(callback : Function){ removeSpaceEventListner(callback : Function){
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback); this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
} }
destroy(): void {
this.joystick.destroy();
}
} }

View File

@ -17,6 +17,8 @@ import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore"; import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
import {iframeListener} from "./Api/IframeListener"; import {iframeListener} from "./Api/IframeListener";
import {HdpiManager} from "./Phaser/Services/HdpiManager";
import {waScaleManager} from "./Phaser/Services/WaScaleManager";
const {width, height} = coWebsiteManager.getGameSize(); const {width, height} = coWebsiteManager.getGameSize();
@ -67,16 +69,22 @@ switch (phaserMode) {
throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"'); throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"');
} }
const hdpiManager = new HdpiManager(640*480);
const { game: gameSize, real: realSize } = hdpiManager.getOptimalGameSize({width, height});
const config: GameConfig = { const config: GameConfig = {
type: mode, type: mode,
title: "WorkAdventure", title: "WorkAdventure",
width: width / 2, scale: {
height: height / 2, parent: "game",
parent: "game", width: gameSize.width,
zoom: 2, height: gameSize.height,
zoom: realSize.width / gameSize.width,
autoRound: true,
resizeInterval: 999999999999
},
scene: [EntryScene, LoginScene, SelectCharacterScene, SelectCompanionScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene], scene: [EntryScene, LoginScene, SelectCharacterScene, SelectCompanionScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
resolution: window.devicePixelRatio / 2, //resolution: window.devicePixelRatio / 2,
fps: fps, fps: fps,
dom: { dom: {
createContainer: true createContainer: true
@ -105,10 +113,13 @@ const config: GameConfig = {
const game = new Phaser.Game(config); const game = new Phaser.Game(config);
waScaleManager.setScaleManager(game.scale);
waScaleManager.applyNewSize();
window.addEventListener('resize', function (event) { window.addEventListener('resize', function (event) {
coWebsiteManager.resetStyle(); coWebsiteManager.resetStyle();
const {width, height} = coWebsiteManager.getGameSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION); waScaleManager.applyNewSize();
// Let's trigger the onResize method of any active scene that is a ResizableScene // Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of game.scene.getScenes(true)) { for (const scene of game.scene.getScenes(true)) {
@ -119,8 +130,7 @@ window.addEventListener('resize', function (event) {
}); });
coWebsiteManager.onResize.subscribe(() => { coWebsiteManager.onResize.subscribe(() => {
const {width, height} = coWebsiteManager.getGameSize(); waScaleManager.applyNewSize();
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
}); });
iframeListener.init(); iframeListener.init();

View File

@ -0,0 +1,32 @@
import "jasmine";
import {HdpiManager} from "../../../src/Phaser/Services/HdpiManager";
describe("Test HdpiManager", () => {
it("should match screen size if size is too small.", () => {
const hdpiManager = new HdpiManager(640*480);
const result = hdpiManager.getOptimalGameSize({ width: 320, height: 200 });
expect(result.game.width).toEqual(320);
expect(result.game.height).toEqual(200);
expect(result.real.width).toEqual(320);
expect(result.real.height).toEqual(200);
});
it("should match multiple just above.", () => {
const hdpiManager = new HdpiManager(640*480);
let result = hdpiManager.getOptimalGameSize({ width: 960, height: 600 });
expect(result.game.width).toEqual(960);
expect(result.game.height).toEqual(600);
result = hdpiManager.getOptimalGameSize({ width: 640 * 2 + 50, height: 480 * 2 + 50 });
expect(result.game.width).toEqual(Math.ceil((640 * 2 + 50) / 2));
expect(result.game.height).toEqual((480 * 2 + 50) / 2);
result = hdpiManager.getOptimalGameSize({ width: 640 * 3 + 50, height: 480 * 3 + 50 });
expect(result.game.width).toEqual(Math.ceil((640 * 3 + 50) / 3));
expect(result.game.height).toEqual(Math.ceil((480 * 3 + 50) / 3));
expect(result.real.width).toEqual(result.game.width * 3);
expect(result.real.height).toEqual(result.game.height * 3);
});
});