Merge pull request #898 from thecodingmachine/touchZoom

FEATURE: add the ability to zoom in and out using touch screen
This commit is contained in:
Kharhamel 2021-04-20 14:51:08 +02:00 committed by GitHub
commit 5dc2f0ac47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 150 additions and 49 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,35 @@
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
const outOfScreenX = -1000;
const outOfScreenY = -1000;
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
export const joystickBaseKey = 'joystickBase';
export const joystickBaseImg = 'resources/objects/joystickSplitted.png';
export const joystickThumbKey = 'joystickThumb';
export const joystickThumbImg = 'resources/objects/smallHandleFilledGrey.png';
export class MobileJoystick extends VirtualJoystick {
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),
enable: true,
dir: "8dir",
});
this.scene.input.on('pointerdown', (pointer: { x: number; y: number; }) => {
this.x = pointer.x;
this.y = pointer.y;
});
this.scene.input.on('pointerup', () => {
this.x = outOfScreenX;
this.y = outOfScreenY;
});
}
}

View File

@ -34,7 +34,7 @@ export class GameManager {
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> { public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
this.startRoom = await connectionManager.initGameConnexion(); this.startRoom = await connectionManager.initGameConnexion();
await this.loadMap(this.startRoom, scenePlugin); await this.loadMap(this.startRoom, scenePlugin);
if (!this.playerName) { if (!this.playerName) {
return LoginSceneName; return LoginSceneName;
} else if (!this.characterLayers || !this.characterLayers.length) { } else if (!this.characterLayers || !this.characterLayers.length) {

View File

@ -58,10 +58,6 @@ import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory"; import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager"; import {urlManager} from "../../Url/UrlManager";
import {audioManager} from "../../WebRtc/AudioManager"; import {audioManager} from "../../WebRtc/AudioManager";
import {IVirtualJoystick} from "../../types";
const {
default: VirtualJoystick,
} = require("phaser3-rex-plugins/plugins/virtualjoystick.js");
import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import {PresentationModeIcon} from "../Components/PresentationModeIcon";
import {ChatModeIcon} from "../Components/ChatModeIcon"; import {ChatModeIcon} from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
@ -81,6 +77,9 @@ import DOMElement = Phaser.GameObjects.DOMElement;
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface|null,
@ -175,7 +174,6 @@ export class GameScene extends ResizableScene implements CenterListener {
private messageSubscription: Subscription|null = null; private messageSubscription: Subscription|null = null;
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;
public virtualJoystick!: IVirtualJoystick;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({ super({
@ -199,6 +197,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook preload scene //hook preload scene
preload(): void { preload(): void {
addLoader(this);
const localUser = localUserStore.getLocalUser(); const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures; const textures = localUser?.textures;
if (textures) { if (textures) {
@ -208,6 +207,10 @@ export class GameScene extends ResizableScene implements CenterListener {
} }
this.load.image(openChatIconName, 'resources/objects/talk.png'); this.load.image(openChatIconName, 'resources/objects/talk.png');
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg);
}
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
@ -254,8 +257,6 @@ export class GameScene extends ResizableScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
addLoader(this);
} }
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
@ -358,6 +359,10 @@ export class GameScene extends ResizableScene implements CenterListener {
urlManager.pushRoomIdToUrl(this.room); urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl(); this.startLayerName = urlManager.getStartLayerNameFromUrl();
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError()) this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
const playerName = gameManager.getPlayerName(); const playerName = gameManager.getPlayerName();
@ -411,26 +416,10 @@ 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});
this.virtualJoystick = new VirtualJoystick(this, {
x: this.game.renderer.width / 2,
y: this.game.renderer.height / 2,
radius: 20,
base: this.add.circle(0, 0, 20),
thumb: this.add.circle(0, 0, 10),
enable: true,
dir: "8dir",
});
this.virtualJoystick.visible = true;
//create input to move //create input to move
mediaManager.setUserInputManager(this.userInputManager); mediaManager.setUserInputManager(this.userInputManager);
this.userInputManager = new UserInputManager(this, this.virtualJoystick); this.userInputManager = new UserInputManager(this);
// Listener event to reposition virtual joystick
// whatever place you click in game area
this.input.on('pointerdown', (pointer: { x: number; y: number; }) => {
this.virtualJoystick.x = pointer.x;
this.virtualJoystick.y = pointer.y;
});
if (localUserStore.getFullscreen()) { if (localUserStore.getFullscreen()) {
document.querySelector('body')?.requestFullscreen(); document.querySelector('body')?.requestFullscreen();

View File

@ -7,6 +7,8 @@ import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter"; import {SoundMeter} from "../Components/SoundMeter";
import {SoundMeterSprite} from "../Components/SoundMeterSprite"; import {SoundMeterSprite} from "../Components/SoundMeterSprite";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
export const EnableCameraSceneName = "EnableCameraScene"; export const EnableCameraSceneName = "EnableCameraScene";
enum LoginTextures { enum LoginTextures {
@ -54,6 +56,10 @@ export class EnableCameraScene extends Phaser.Scene {
} }
create() { create() {
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.textField = new TextField(this, this.game.renderer.width / 2, 20, 'Turn on your camera and microphone'); this.textField = new TextField(this, this.game.renderer.width / 2, 20, 'Turn on your camera and microphone');
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Touch here\n\n or \n\nPress enter to start'); this.pressReturnField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 30, 'Touch here\n\n or \n\nPress enter to start');

View File

@ -7,6 +7,8 @@ import {ResizableScene} from "./ResizableScene";
import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser";
import { localUserStore } from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene"; export const LoginSceneName = "LoginScene";
@ -39,6 +41,9 @@ export class LoginScene extends ResizableScene {
} }
create() { create() {
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => { this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => {
this.name = text; this.name = text;

View File

@ -4,13 +4,14 @@ import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {EnableCameraSceneName} from "./EnableCameraScene"; import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene"; import {CustomizeSceneName} from "./CustomizeScene";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
import {addLoader} from "../Components/Loader"; import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene"; import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser"; import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
@ -66,6 +67,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
} }
create() { create() {
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character'); this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your character');
this.pressReturnField = new TextField( this.pressReturnField = new TextField(
this, this,

View File

@ -8,6 +8,8 @@ import { EnableCameraSceneName } from "./EnableCameraScene";
import { localUserStore } from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures"; import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager"; import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
export const SelectCompanionSceneName = "SelectCompanionScene"; export const SelectCompanionSceneName = "SelectCompanionScene";
@ -54,6 +56,10 @@ export class SelectCompanionScene extends ResizableScene {
} }
create() { create() {
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion'); this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion');
const confirmTouchAreaY = 115 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow); const confirmTouchAreaY = 115 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow);

View File

@ -0,0 +1,22 @@
import {Pinch} from "phaser3-rex-plugins/plugins/gestures.js";
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.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;
}
this.scene.cameras.main.setZoom(newZoom);
});
}
}

View File

@ -1,8 +1,7 @@
import { Direction, IVirtualJoystick } from "../../types"; import { Direction } from "../../types";
import {GameScene} from "../Game/GameScene"; import {GameScene} from "../Game/GameScene";
const { import {touchScreenManager} from "../../Touch/TouchScreenManager";
default: VirtualJoystick, import {MobileJoystick} from "../Components/MobileJoystick";
} = require("phaser3-rex-plugins/plugins/virtualjoystick.js");
interface UserInputManagerDatum { interface UserInputManagerDatum {
keyInstance: Phaser.Input.Keyboard.Key; keyInstance: Phaser.Input.Keyboard.Key;
@ -20,6 +19,7 @@ export enum UserInputEvent {
JoystickMove, JoystickMove,
} }
//we cannot use a map structure so we have to create a replacment //we cannot use a map structure so we have to create a replacment
export class ActiveEventList { export class ActiveEventList {
private eventMap : Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>(); private eventMap : Map<UserInputEvent, boolean> = new Map<UserInputEvent, boolean>();
@ -44,17 +44,23 @@ export class UserInputManager {
private Scene: GameScene; private Scene: GameScene;
private isInputDisabled : boolean; private isInputDisabled : boolean;
private joystick : IVirtualJoystick; private joystick!: MobileJoystick;
private joystickEvents = new ActiveEventList(); private joystickEvents = new ActiveEventList();
private joystickForceThreshold = 60; private joystickForceThreshold = 60;
private joystickForceAccuX = 0; private joystickForceAccuX = 0;
private joystickForceAccuY = 0; private joystickForceAccuY = 0;
constructor(Scene: GameScene, virtualJoystick: IVirtualJoystick) { constructor(Scene: GameScene) {
this.Scene = Scene; this.Scene = Scene;
this.isInputDisabled = false; this.isInputDisabled = false;
this.initKeyBoardEvent(); this.initKeyBoardEvent();
this.joystick = virtualJoystick; if (touchScreenManager.supportTouchScreen) {
this.initVirtualJoystick();
}
}
initVirtualJoystick() {
this.joystick = new MobileJoystick(this.Scene);
this.joystick.on("update", () => { this.joystick.on("update", () => {
this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0; this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0;
this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0; this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0;
@ -62,18 +68,18 @@ export class UserInputManager {
for (const name in cursorKeys) { for (const name in cursorKeys) {
const key = cursorKeys[name as Direction]; const key = cursorKeys[name as Direction];
switch (name) { switch (name) {
case "up": case "up":
this.joystickEvents.set(UserInputEvent.MoveUp, key.isDown); this.joystickEvents.set(UserInputEvent.MoveUp, key.isDown);
break; break;
case "left": case "left":
this.joystickEvents.set(UserInputEvent.MoveLeft, key.isDown); this.joystickEvents.set(UserInputEvent.MoveLeft, key.isDown);
break; break;
case "down": case "down":
this.joystickEvents.set(UserInputEvent.MoveDown, key.isDown); this.joystickEvents.set(UserInputEvent.MoveDown, key.isDown);
break; break;
case "right": case "right":
this.joystickEvents.set(UserInputEvent.MoveRight, key.isDown); this.joystickEvents.set(UserInputEvent.MoveRight, key.isDown);
break; break;
} }
} }
}); });
@ -105,6 +111,7 @@ export class UserInputManager {
this.Scene.input.keyboard.removeAllListeners(); this.Scene.input.keyboard.removeAllListeners();
} }
//todo: should we also disable the joystick?
disableControls(){ disableControls(){
this.Scene.input.keyboard.removeAllKeys(); this.Scene.input.keyboard.removeAllKeys();
this.isInputDisabled = true; this.isInputDisabled = true;

View File

@ -0,0 +1,16 @@
class TouchScreenManager {
readonly supportTouchScreen:boolean;
constructor() {
this.supportTouchScreen = this.detectTouchscreen();
}
//found here: https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript#4819886
detectTouchscreen(): boolean {
return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
}
}
export const touchScreenManager = new TouchScreenManager();

View File

@ -2,7 +2,7 @@ import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig; import GameConfig = Phaser.Types.Core.GameConfig;
import "../dist/resources/style/index.scss"; import "../dist/resources/style/index.scss";
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable";
import {LoginScene} from "./Phaser/Login/LoginScene"; import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
@ -17,7 +17,6 @@ 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 {discussionManager} from "./WebRtc/DiscussionManager";
const {width, height} = coWebsiteManager.getGameSize(); const {width, height} = coWebsiteManager.getGameSize();

12
front/src/rex-plugins.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
declare module 'phaser3-rex-plugins/plugins/virtualjoystick.js' {
const content: any; // eslint-disable-line
export default content;
}
declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' {
const content: any; // eslint-disable-line
export default content;
}
declare module 'phaser3-rex-plugins/plugins/gestures.js' {
export const Pinch: any; // eslint-disable-line
}