diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 73668cc9..81305671 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -153,23 +153,6 @@ services: - "traefik.http.routers.uploader-ssl.tls=true" - "traefik.http.routers.uploader-ssl.service=uploader" - website: - image: thecodingmachine/nodejs:12-apache - environment: - STARTUP_COMMAND_1: npm install - STARTUP_COMMAND_2: npm run watch & - APACHE_DOCUMENT_ROOT: dist/ - volumes: - - ./website:/var/www/html - labels: - - "traefik.http.routers.website.rule=Host(`workadventure.localhost`)" - - "traefik.http.routers.website.entryPoints=web" - - "traefik.http.services.website.loadbalancer.server.port=80" - - "traefik.http.routers.website-ssl.rule=Host(`workadventure.localhost`)" - - "traefik.http.routers.website-ssl.entryPoints=websecure" - - "traefik.http.routers.website-ssl.tls=true" - - "traefik.http.routers.website-ssl.service=website" - messages: #image: thecodingmachine/nodejs:14 image: thecodingmachine/workadventure-back-base:latest diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 213b00f2..eb2f949f 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -381,7 +381,7 @@ body { max-height: 25%; } - + } #game { @@ -540,7 +540,7 @@ input[type=range]:focus::-ms-fill-upper { position: absolute; width: 100%; height: 100%; - pointer-events: all; + pointer-events: none; /* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */ } @@ -549,7 +549,7 @@ input[type=range]:focus::-ms-fill-upper { } .game-overlay video { - width: 100% + width: 100%; } .main-section { @@ -565,6 +565,7 @@ input[type=range]:focus::-ms-fill-upper { flex-basis: 96%; transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s; cursor: url('/resources/logos/cursor_pointer.png'), pointer; + pointer-events: auto; /*flex-shrink: 2;*/ } @@ -576,7 +577,6 @@ input[type=range]:focus::-ms-fill-upper { .sidebar { flex: 0 0 25%; display: flex; - pointer-events: all; } .sidebar > div { @@ -584,6 +584,7 @@ input[type=range]:focus::-ms-fill-upper { transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s; cursor: url('/resources/logos/cursor_pointer.png'), pointer; border-radius: 15px 15px 15px 15px; + pointer-events: auto; } .sidebar > div:hover { diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 5d6a5ea6..ca1bd6d2 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -11,7 +11,7 @@ const TURN_PASSWORD: string = process.env.TURN_PASSWORD || ''; 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 RESOLUTION = 2; -const ZOOM_LEVEL = 1/*3/4*/; +const ZOOM_LEVEL = 1; 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 export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; diff --git a/front/src/Phaser/Components/MobileJoystick.ts b/front/src/Phaser/Components/MobileJoystick.ts index 1ace529c..0af3d6c8 100644 --- a/front/src/Phaser/Components/MobileJoystick.ts +++ b/front/src/Phaser/Components/MobileJoystick.ts @@ -1,8 +1,6 @@ import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js'; - -const outOfScreenX = -1000; -const outOfScreenY = -1000; - +import ScaleManager = Phaser.Scale.ScaleManager; +import {waScaleManager} from "../Services/WaScaleManager"; //the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free export const joystickBaseKey = 'joystickBase'; @@ -10,26 +8,58 @@ export const joystickBaseImg = 'resources/objects/joystickSplitted.png'; export const joystickThumbKey = 'joystickThumb'; export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png'; +const baseSize = 50; +const thumbSize = 25; +const radius = 17.5; + export class MobileJoystick extends VirtualJoystick { - + private resizeCallback: () => void; + constructor(scene: Phaser.Scene) { super(scene, { - x: outOfScreenX, - y: outOfScreenY, - radius: 20, - base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(60, 60).setDepth(99999), - thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(30, 30).setDepth(99999), + x: -1000, + y: -1000, + radius: radius * window.devicePixelRatio, + base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(99999), + thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(99999), enable: true, dir: "8dir", }); + this.visible = false; + this.enable = false; - this.scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => { - this.x = pointer.x; - this.y = pointer.y; + this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => { + if (!pointer.wasTouch) { + return; + } + + // Let's only display the joystick if there is one finger on the screen + if (pointer.event.touches.length === 1) { + this.x = pointer.x; + this.y = pointer.y; + this.visible = true; + this.enable = true; + } else { + this.visible = false; + this.enable = false; + } }); this.scene.input.on('pointerup', () => { - this.x = outOfScreenX; - this.y = outOfScreenY; + this.visible = false; + this.enable = false; }); + this.resizeCallback = this.resize.bind(this); + this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback); } -} \ No newline at end of file + + private resize() { + this.base.setDisplaySize(baseSize / waScaleManager.zoomModifier * window.devicePixelRatio, baseSize / waScaleManager.zoomModifier * window.devicePixelRatio); + this.thumb.setDisplaySize(thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio, thumbSize / waScaleManager.zoomModifier * window.devicePixelRatio); + this.setRadius(radius / waScaleManager.zoomModifier * window.devicePixelRatio); + } + + public destroy() { + super.destroy(); + this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback); + } +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c433ed0f..4500779f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -89,6 +89,7 @@ import {TextUtils} from "../Components/TextUtils"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; +import {waScaleManager} from "../Services/WaScaleManager"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -183,6 +184,7 @@ export class GameScene extends ResizableScene implements CenterListener { private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); private originalMapUrl: string|undefined; + private pinchManager: PinchManager|undefined; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ @@ -201,7 +203,7 @@ export class GameScene extends ResizableScene implements CenterListener { }) this.connectionAnswerPromise = new Promise((resolve, reject): void => { this.connectionAnswerPromiseResolve = resolve; - }) + }); } //hook preload scene @@ -371,7 +373,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.startLayerName = urlManager.getStartLayerNameFromUrl(); if (touchScreenManager.supportTouchScreen) { - new PinchManager(this); + this.pinchManager = new PinchManager(this); } this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError(message)) @@ -914,6 +916,8 @@ ${escapedMessage} this.simplePeer?.closeAllConnections(); this.simplePeer?.unregister(); this.messageSubscription?.unsubscribe(); + this.userInputManager.destroy(); + this.pinchManager?.destroy(); for(const iframeEvents of this.iframeSubscriptionList){ iframeEvents.unsubscribe(); @@ -1061,8 +1065,8 @@ ${escapedMessage} //todo: in a dedicated class/function? initCamera() { this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); + this.cameras.main.startFollow(this.CurrentPlayer, true); this.updateCameraOffset(); - this.cameras.main.setZoom(ZOOM_LEVEL); } addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ @@ -1435,19 +1439,18 @@ ${escapedMessage} } /** - * Updates the offset of the character compared to the center of the screen according to the layout mananger - * (tries to put the character in the center of the reamining space if there is a discussion going on. + * 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 remaining space if there is a discussion going on. */ private updateCameraOffset(): void { const array = layoutManager.findBiggestAvailableArray(); - let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart; - let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart; + const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart; + const yCenter = (array.yEnd - array.yStart) / 2 + array.yStart; + const game = HtmlUtils.querySelectorOrFail('#game canvas'); // Let's put this in Game coordinates by applying the zoom level: - xCenter /= ZOOM_LEVEL * RESOLUTION; - yCenter /= ZOOM_LEVEL * RESOLUTION; - this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, 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 { @@ -1510,4 +1513,9 @@ ${escapedMessage} }); } } + + zoomByFactor(zoomFactor: number) { + waScaleManager.zoomModifier *= zoomFactor; + this.updateCameraOffset(); + } } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 917dd44b..1af0c092 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -3,7 +3,7 @@ import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; import {mediaManager} from "../../WebRtc/MediaManager"; -import {RESOLUTION} from "../../Enum/EnvironmentVariable"; +import {RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {SoundMeter} from "../Components/SoundMeter"; import {SoundMeterSprite} from "../Components/SoundMeterSprite"; import {HtmlUtils} from "../../WebRtc/HtmlUtils"; @@ -42,6 +42,7 @@ export class EnableCameraScene extends Phaser.Scene { private enableCameraSceneElement!: Phaser.GameObjects.DOMElement; private mobileTapZone!: Zone; + constructor() { super({ key: EnableCameraSceneName @@ -75,12 +76,14 @@ export class EnableCameraScene extends Phaser.Scene { if (touchScreenManager.supportTouchScreen) { new PinchManager(this); } - + //this.scale.setZoom(ZOOM_LEVEL); + //Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone); + /* FIX ME */ - this.textField = new TextField(this, this.game.renderer.width / 2, 20, ''); + this.textField = new TextField(this, this.scale.width / 2, 20, ''); // For mobile purposes - we need a big enough touchable area. - this.mobileTapZone = this.add.zone(this.game.renderer.width / 2,this.game.renderer.height - 30,200,50) + this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50) .setInteractive().on("pointerdown", () => { this.login(); }); @@ -243,6 +246,11 @@ export class EnableCameraScene extends Phaser.Scene { this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16; this.arrowUp.y = this.microphoneNameField.y; + + const actionBtn = document.querySelector('#enableCameraScene .action'); + if (actionBtn !== null) { + actionBtn.style.top = (this.scale.height - 65) + 'px'; + } } update(time: number, delta: number): void { @@ -256,6 +264,7 @@ export class EnableCameraScene extends Phaser.Scene { duration: 1000, ease: 'Power3' }); + } private login(): void { @@ -283,12 +292,12 @@ export class EnableCameraScene extends Phaser.Scene { } private getMiddleX() : number{ - return (this.game.renderer.width / RESOLUTION) - + return (this.scale.width / 2) - ( this.enableCameraSceneElement && this.enableCameraSceneElement.node && this.enableCameraSceneElement.node.getBoundingClientRect().width > 0 - ? (this.enableCameraSceneElement.node.getBoundingClientRect().width / (2*RESOLUTION)) + ? (this.enableCameraSceneElement.node.getBoundingClientRect().width / 2 / this.scale.zoom) : (300 / RESOLUTION) ); } diff --git a/front/src/Phaser/Login/EntryScene.ts b/front/src/Phaser/Login/EntryScene.ts index e7650750..b85b3f56 100644 --- a/front/src/Phaser/Login/EntryScene.ts +++ b/front/src/Phaser/Login/EntryScene.ts @@ -2,6 +2,7 @@ import {gameManager} from "../Game/GameManager"; import {Scene} from "phaser"; import {ErrorScene} from "../Reconnecting/ErrorScene"; import {WAError} from "../Reconnecting/WAError"; +import {waScaleManager} from "../Services/WaScaleManager"; export const EntrySceneName = "EntryScene"; @@ -17,7 +18,11 @@ export class EntryScene extends Scene { } create() { + gameManager.init(this.scene).then((nextSceneName) => { + // Let's rescale before starting the game + // We can do it at this stage. + waScaleManager.applyNewSize(); this.scene.start(nextSceneName); }).catch((err) => { if (err.response && err.response.status == 404) { diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 05cea305..c67c7119 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -192,11 +192,11 @@ export class MenuScene extends Phaser.Scene { } }); - let middleY = (window.innerHeight / 3) - (257); + let middleY = this.scale.height / 2 - 392/2; if(middleY < 0){ middleY = 0; } - let middleX = (window.innerWidth / 3) - 298; + let middleX = this.scale.width / 2 - 457/2; if(middleX < 0){ middleX = 0; } @@ -236,11 +236,11 @@ export class MenuScene extends Phaser.Scene { this.gameShareOpened = true; - let middleY = (window.innerHeight / 3) - (257); + let middleY = this.scale.height / 2 - 85; if(middleY < 0){ middleY = 0; } - let middleX = (window.innerWidth / 3) - 298; + let middleX = this.scale.width / 2 - 200; if(middleX < 0){ middleX = 0; } diff --git a/front/src/Phaser/Services/HdpiManager.ts b/front/src/Phaser/Services/HdpiManager.ts new file mode 100644 index 00000000..867c7a53 --- /dev/null +++ b/front/src/Phaser/Services/HdpiManager.ts @@ -0,0 +1,105 @@ +import ScaleManager = Phaser.Scale.ScaleManager; + +interface Size { + width: number; + height: number; +} + +export class HdpiManager { + private _zoomModifier: number = 1; + + /** + * + * @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user + * @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more + */ + public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: 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.minRecommendedGamePixelsNumber) { + return { + game: realPixelScreenSize, + real: realPixelScreenSize + }; + } + + let i = 1; + + while (realPixelNumber > this.minRecommendedGamePixelsNumber * i * i) { + i++; + } + + // Has the canvas more pixels than the screen? This is forbidden + if ((i - 1) * this._zoomModifier < 1) { + // Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter) + this._zoomModifier = 1 / (i - 1); + + return { + game: { + width: realPixelScreenSize.width, + height: realPixelScreenSize.height, + }, + real: { + width: realPixelScreenSize.width, + height: realPixelScreenSize.height, + } + } + } + + const gameWidth = Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier); + const gameHeight = Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier); + + // Let's ensure we display a minimum of pixels, even if crazily zoomed in. + if (gameWidth * gameHeight < this.absoluteMinPixelNumber) { + const minGameHeight = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.height / realPixelScreenSize.width); + const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height); + + // Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter) + this._zoomModifier = realPixelScreenSize.width / minGameWidth / (i - 1); + + return { + game: { + width: minGameWidth, + height: minGameHeight, + }, + real: { + width: realPixelScreenSize.width, + height: realPixelScreenSize.height, + } + } + + } + + return { + game: { + width: gameWidth, + height: gameHeight, + }, + 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; + } +} diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts new file mode 100644 index 00000000..dfac135e --- /dev/null +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -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, private absoluteMinPixelNumber: number) { + this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber); + } + + 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, 196*196); diff --git a/front/src/Phaser/UserInput/PinchManager.ts b/front/src/Phaser/UserInput/PinchManager.ts index f7c445fa..3174c6ad 100644 --- a/front/src/Phaser/UserInput/PinchManager.ts +++ b/front/src/Phaser/UserInput/PinchManager.ts @@ -1,22 +1,41 @@ import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {GameScene} from "../Game/GameScene"; export class PinchManager { private scene: Phaser.Scene; private pinch!: any; // eslint-disable-line - + constructor(scene: Phaser.Scene) { this.scene = scene; this.pinch = new Pinch(scene); + this.pinch.setDragThreshold(10); + + // The "pinch.scaleFactor" value is very sensitive and causes the screen to flicker. + // We are smoothing its value with previous values to prevent the flicking. + let smoothPinch = 1; + + this.pinch.on('pinchstart', () => { + smoothPinch = 1; + }); + this.pinch.on('pinch', (pinch:any) => { // eslint-disable-line - let newZoom = this.scene.cameras.main.zoom * pinch.scaleFactor; - if (newZoom < 0.25) { - newZoom = 0.25; - } else if(newZoom > 2) { - newZoom = 2; + if (pinch.scaleFactor > 1.2 || pinch.scaleFactor < 0.8) { + // Pinch too fast! Probably a bad measure. + return; + } + + smoothPinch = 3/5*smoothPinch + 2/5*pinch.scaleFactor; + if (this.scene instanceof GameScene) { + this.scene.zoomByFactor(smoothPinch); + } else { + waScaleManager.zoomModifier *= smoothPinch; } - this.scene.cameras.main.setZoom(newZoom); }); } - -} \ No newline at end of file + + destroy() { + this.pinch.removeAllListeners(); + } +} diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 2f14672b..fe78dac8 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -54,11 +54,12 @@ export class UserInputManager { this.Scene = Scene; this.isInputDisabled = false; this.initKeyBoardEvent(); + this.initMouseWheel(); if (touchScreenManager.supportTouchScreen) { this.initVirtualJoystick(); } } - + initVirtualJoystick() { this.joystick = new MobileJoystick(this.Scene); this.joystick.on("update", () => { @@ -170,4 +171,14 @@ export class UserInputManager { removeSpaceEventListner(callback : Function){ this.Scene.input.keyboard.removeListener('keyup-SPACE', callback); } + + destroy(): void { + this.joystick.destroy(); + } + + private initMouseWheel() { + this.Scene.input.on('wheel', (pointer: unknown, gameObjects: unknown, deltaX: number, deltaY: number, deltaZ: number) => { + this.Scene.zoomByFactor(1 - deltaY / 53 * 0.1); + }); + } } diff --git a/front/src/index.ts b/front/src/index.ts index 26155146..0bdf3aa2 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -18,6 +18,8 @@ import {localUserStore} from "./Connexion/LocalUserStore"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {iframeListener} from "./Api/IframeListener"; import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobileScene'; +import {HdpiManager} from "./Phaser/Services/HdpiManager"; +import {waScaleManager} from "./Phaser/Services/WaScaleManager"; const {width, height} = coWebsiteManager.getGameSize(); @@ -68,23 +70,31 @@ switch (phaserMode) { throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"'); } +const hdpiManager = new HdpiManager(640*480, 196*196); +const { game: gameSize, real: realSize } = hdpiManager.getOptimalGameSize({width, height}); + const config: GameConfig = { type: mode, title: "WorkAdventure", - width: width / RESOLUTION, - height: height / RESOLUTION, - parent: "game", - scene: [EntryScene, + scale: { + parent: "game", + width: gameSize.width, + height: gameSize.height, + zoom: realSize.width / gameSize.width, + autoRound: true, + resizeInterval: 999999999999 + }, + scene: [EntryScene, LoginScene, isMobile() ? SelectCharacterMobileScene : SelectCharacterScene, - SelectCompanionScene, - EnableCameraScene, - ReconnectingScene, - ErrorScene, - CustomizeScene, - MenuScene, + SelectCompanionScene, + EnableCameraScene, + ReconnectingScene, + ErrorScene, + CustomizeScene, + MenuScene, HelpCameraSettingsScene], - zoom: RESOLUTION, + //resolution: window.devicePixelRatio / 2, fps: fps, dom: { createContainer: true @@ -113,10 +123,12 @@ const config: GameConfig = { const game = new Phaser.Game(config); +waScaleManager.setScaleManager(game.scale); + window.addEventListener('resize', function (event) { 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 for (const scene of game.scene.getScenes(true)) { @@ -127,8 +139,7 @@ window.addEventListener('resize', function (event) { }); coWebsiteManager.onResize.subscribe(() => { - const {width, height} = coWebsiteManager.getGameSize(); - game.scale.resize(width / RESOLUTION, height / RESOLUTION); + waScaleManager.applyNewSize(); }); iframeListener.init(); diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts index 7ba8f65b..d5457702 100644 --- a/front/src/rex-plugins.d.ts +++ b/front/src/rex-plugins.d.ts @@ -4,9 +4,9 @@ declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' { export default content; } declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' { - const content: any; // eslint-disable-line + const content: any; // eslint-disable-line export default content; } declare module 'phaser3-rex-plugins/plugins/gestures.js' { export const Pinch: any; // eslint-disable-line -} \ No newline at end of file +} diff --git a/front/tests/Phaser/Services/HdpiManagerTest.ts b/front/tests/Phaser/Services/HdpiManagerTest.ts new file mode 100644 index 00000000..32a6b03a --- /dev/null +++ b/front/tests/Phaser/Services/HdpiManagerTest.ts @@ -0,0 +1,55 @@ +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, 64*64); + + 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, 64*64); + + 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); + }); + + it("should not zoom in too much.", () => { + const hdpiManager = new HdpiManager(640*480, 64*64); + + hdpiManager.zoomModifier = 11; + + const result = hdpiManager.getOptimalGameSize({ width: 640, height: 640 }); + expect(result.game.width).toEqual(64); + expect(result.game.height).toEqual(64); + expect(hdpiManager.zoomModifier).toEqual(10); + + }); + + it("should not zoom out too much.", () => { + const hdpiManager = new HdpiManager(640*480, 64*64); + + hdpiManager.zoomModifier = 1/10; + + const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 }); + expect(result.game.width).toEqual(1280); + expect(result.game.height).toEqual(768); + expect(hdpiManager.zoomModifier).toEqual(1); + }); +}); diff --git a/maps/tests/autoresize.json b/maps/tests/autoresize.json new file mode 100644 index 00000000..39db9874 --- /dev/null +++ b/maps/tests/autoresize.json @@ -0,0 +1,82 @@ +{ "compressionlevel":-1, + "height":25, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":25, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":25, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":25, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":25, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":114.5, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nOpen your browser to the maximum size of your screen. Resize the browser window just smaller than the blue \"carpet\".\nResult:\nThe viewport is zoomed out x2 so that you can still see the \"carpet\"", + "wrap":true + }, + "type":"", + "visible":true, + "width":252.4375, + "x":162.78125, + "y":129.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":5, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"2021.03.23", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.5, + "width":25 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index f53bbae9..554125df 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -42,6 +42,30 @@ Testing scripting API with a script + + + Success Failure Pending + + + Testing auto-zoom of viewport + + + + + Success Failure Pending + + + Testing zoom via mouse wheel + + + + + Success Failure Pending + + + Testing movement on mobile + +