Merge branch 'develop' of github.com:thecodingmachine/workadventure into feature-picture-of-user-merge
This commit is contained in:
commit
8efeab97c6
60 changed files with 1609 additions and 144 deletions
178
front/src/Phaser/Game/CameraManager.ts
Normal file
178
front/src/Phaser/Game/CameraManager.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
import { Easing } from "../../types";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import type { Box } from "../../WebRtc/LayoutManager";
|
||||
import type { Player } from "../Player/Player";
|
||||
import type { WaScaleManager } from "../Services/WaScaleManager";
|
||||
import type { GameScene } from "./GameScene";
|
||||
|
||||
export enum CameraMode {
|
||||
Free = "Free",
|
||||
Follow = "Follow",
|
||||
Focus = "Focus",
|
||||
}
|
||||
|
||||
export class CameraManager extends Phaser.Events.EventEmitter {
|
||||
private scene: GameScene;
|
||||
private camera: Phaser.Cameras.Scene2D.Camera;
|
||||
private cameraBounds: { x: number; y: number };
|
||||
private waScaleManager: WaScaleManager;
|
||||
|
||||
private cameraMode: CameraMode = CameraMode.Free;
|
||||
|
||||
private restoreZoomTween?: Phaser.Tweens.Tween;
|
||||
private startFollowTween?: Phaser.Tweens.Tween;
|
||||
|
||||
private cameraFollowTarget?: { x: number; y: number };
|
||||
|
||||
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
||||
super();
|
||||
this.scene = scene;
|
||||
|
||||
this.camera = scene.cameras.main;
|
||||
this.cameraBounds = cameraBounds;
|
||||
|
||||
this.waScaleManager = waScaleManager;
|
||||
|
||||
this.initCamera();
|
||||
|
||||
this.bindEventHandlers();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.scene.game.events.off("wa-scale-manager:refresh-focus-on-target");
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public getCamera(): Phaser.Cameras.Scene2D.Camera {
|
||||
return this.camera;
|
||||
}
|
||||
|
||||
public enterFocusMode(
|
||||
focusOn: { x: number; y: number; width: number; height: number },
|
||||
margin: number = 0,
|
||||
duration: number = 1000
|
||||
): void {
|
||||
this.setCameraMode(CameraMode.Focus);
|
||||
this.waScaleManager.saveZoom();
|
||||
this.waScaleManager.setFocusTarget(focusOn);
|
||||
|
||||
this.restoreZoomTween?.stop();
|
||||
this.startFollowTween?.stop();
|
||||
const marginMult = 1 + margin;
|
||||
const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor(
|
||||
focusOn.width * marginMult,
|
||||
focusOn.height * marginMult
|
||||
);
|
||||
const currentZoomModifier = this.waScaleManager.zoomModifier;
|
||||
const zoomModifierChange = targetZoomModifier - currentZoomModifier;
|
||||
this.camera.stopFollow();
|
||||
this.cameraFollowTarget = undefined;
|
||||
this.camera.pan(
|
||||
focusOn.x + focusOn.width * 0.5 * marginMult,
|
||||
focusOn.y + focusOn.height * 0.5 * marginMult,
|
||||
duration,
|
||||
Easing.SineEaseOut,
|
||||
true,
|
||||
(camera, progress, x, y) => {
|
||||
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public leaveFocusMode(player: Player): void {
|
||||
this.waScaleManager.setFocusTarget();
|
||||
this.startFollow(player, 1000);
|
||||
this.restoreZoom(1000);
|
||||
}
|
||||
|
||||
public startFollow(target: object | Phaser.GameObjects.GameObject, duration: number = 0): void {
|
||||
this.cameraFollowTarget = target as { x: number; y: number };
|
||||
this.setCameraMode(CameraMode.Follow);
|
||||
if (duration === 0) {
|
||||
this.camera.startFollow(target, true);
|
||||
return;
|
||||
}
|
||||
const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY };
|
||||
this.startFollowTween = this.scene.tweens.addCounter({
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration,
|
||||
ease: Easing.SineEaseOut,
|
||||
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
||||
if (!this.cameraFollowTarget) {
|
||||
return;
|
||||
}
|
||||
const shiftX =
|
||||
(this.cameraFollowTarget.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue();
|
||||
const shiftY =
|
||||
(this.cameraFollowTarget.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue();
|
||||
this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY);
|
||||
},
|
||||
onComplete: () => {
|
||||
this.camera.startFollow(target, true);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public updateCameraOffset(array: Box): void {
|
||||
const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
|
||||
const 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:
|
||||
|
||||
this.camera.setFollowOffset(
|
||||
((xCenter - game.offsetWidth / 2) * window.devicePixelRatio) / this.scene.scale.zoom,
|
||||
((yCenter - game.offsetHeight / 2) * window.devicePixelRatio) / this.scene.scale.zoom
|
||||
);
|
||||
}
|
||||
|
||||
public isCameraLocked(): boolean {
|
||||
return this.cameraMode === CameraMode.Focus;
|
||||
}
|
||||
|
||||
private setCameraMode(mode: CameraMode): void {
|
||||
if (this.cameraMode === mode) {
|
||||
return;
|
||||
}
|
||||
this.cameraMode = mode;
|
||||
}
|
||||
|
||||
private restoreZoom(duration: number = 0): void {
|
||||
if (duration === 0) {
|
||||
this.waScaleManager.zoomModifier = this.waScaleManager.getSaveZoom();
|
||||
return;
|
||||
}
|
||||
this.restoreZoomTween?.stop();
|
||||
this.restoreZoomTween = this.scene.tweens.addCounter({
|
||||
from: this.waScaleManager.zoomModifier,
|
||||
to: this.waScaleManager.getSaveZoom(),
|
||||
duration,
|
||||
ease: Easing.SineEaseOut,
|
||||
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
||||
this.waScaleManager.zoomModifier = tween.getValue();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private initCamera() {
|
||||
this.camera = this.scene.cameras.main;
|
||||
this.camera.setBounds(0, 0, this.cameraBounds.x, this.cameraBounds.y);
|
||||
}
|
||||
|
||||
private bindEventHandlers(): void {
|
||||
this.scene.game.events.on(
|
||||
"wa-scale-manager:refresh-focus-on-target",
|
||||
(focusOn: { x: number; y: number; width: number; height: number }) => {
|
||||
if (!focusOn) {
|
||||
return;
|
||||
}
|
||||
this.camera.centerOn(focusOn.x + focusOn.width * 0.5, focusOn.y + focusOn.height * 0.5);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiledMap";
|
||||
import type {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapObject,
|
||||
ITiledMapObjectLayer,
|
||||
ITiledMapProperty,
|
||||
} from "../Map/ITiledMap";
|
||||
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
||||
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
import { MathUtils } from "../../Utils/MathUtils";
|
||||
|
||||
export type PropertyChangeCallback = (
|
||||
newValue: string | number | boolean | undefined,
|
||||
|
@ -15,24 +22,48 @@ export type layerChangeCallback = (
|
|||
allLayersOnNewPosition: Array<ITiledMapLayer>
|
||||
) => void;
|
||||
|
||||
export type zoneChangeCallback = (
|
||||
zonesChangedByAction: Array<ITiledMapObject>,
|
||||
allZonesOnNewPosition: Array<ITiledMapObject>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* A wrapper around a ITiledMap interface to provide additional capabilities.
|
||||
* It is used to handle layer properties.
|
||||
*/
|
||||
export class GameMap {
|
||||
// oldKey is the index of the previous tile.
|
||||
/**
|
||||
* oldKey is the index of the previous tile.
|
||||
*/
|
||||
private oldKey: number | undefined;
|
||||
// key is the index of the current tile.
|
||||
/**
|
||||
* key is the index of the current tile.
|
||||
*/
|
||||
private key: number | undefined;
|
||||
/**
|
||||
* oldPosition is the previous position of the player.
|
||||
*/
|
||||
private oldPosition: { x: number; y: number } | undefined;
|
||||
/**
|
||||
* position is the current position of the player.
|
||||
*/
|
||||
private position: { x: number; y: number } | undefined;
|
||||
|
||||
private lastProperties = new Map<string, string | boolean | number>();
|
||||
private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
|
||||
|
||||
private enterLayerCallbacks = Array<layerChangeCallback>();
|
||||
private leaveLayerCallbacks = Array<layerChangeCallback>();
|
||||
private enterZoneCallbacks = Array<zoneChangeCallback>();
|
||||
private leaveZoneCallbacks = Array<zoneChangeCallback>();
|
||||
|
||||
private tileNameMap = new Map<string, number>();
|
||||
|
||||
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
|
||||
public readonly flatLayers: ITiledMapLayer[];
|
||||
public readonly tiledObjects: ITiledMapObject[];
|
||||
public readonly phaserLayers: TilemapLayer[] = [];
|
||||
public readonly zones: ITiledMapObject[] = [];
|
||||
|
||||
public exitUrls: Array<string> = [];
|
||||
|
||||
|
@ -44,6 +75,9 @@ export class GameMap {
|
|||
terrains: Array<Phaser.Tilemaps.Tileset>
|
||||
) {
|
||||
this.flatLayers = flattenGroupLayersMap(map);
|
||||
this.tiledObjects = this.getObjectsFromLayers(this.flatLayers);
|
||||
this.zones = this.tiledObjects.filter((object) => object.type === "zone");
|
||||
|
||||
let depth = -2;
|
||||
for (const layer of this.flatLayers) {
|
||||
if (layer.type === "tilelayer") {
|
||||
|
@ -88,6 +122,10 @@ export class GameMap {
|
|||
* This will trigger events if properties are changing.
|
||||
*/
|
||||
public setPosition(x: number, y: number) {
|
||||
this.oldPosition = this.position;
|
||||
this.position = { x, y };
|
||||
this.triggerZonesChange();
|
||||
|
||||
this.oldKey = this.key;
|
||||
|
||||
const xMap = Math.floor(x / this.map.tilewidth);
|
||||
|
@ -126,7 +164,7 @@ export class GameMap {
|
|||
}
|
||||
}
|
||||
|
||||
private triggerLayersChange() {
|
||||
private triggerLayersChange(): void {
|
||||
const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
|
||||
const layersByNewKey = this.key ? this.getLayersByKey(this.key) : [];
|
||||
|
||||
|
@ -155,6 +193,53 @@ export class GameMap {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use Tiled Objects with type "zone" as zones with defined x, y, width and height for easier event triggering.
|
||||
*/
|
||||
private triggerZonesChange(): void {
|
||||
const zonesByOldPosition = this.oldPosition
|
||||
? this.zones.filter((zone) => {
|
||||
if (!this.oldPosition) {
|
||||
return false;
|
||||
}
|
||||
return MathUtils.isOverlappingWithRectangle(this.oldPosition, zone);
|
||||
})
|
||||
: [];
|
||||
|
||||
const zonesByNewPosition = this.position
|
||||
? this.zones.filter((zone) => {
|
||||
if (!this.position) {
|
||||
return false;
|
||||
}
|
||||
return MathUtils.isOverlappingWithRectangle(this.position, zone);
|
||||
})
|
||||
: [];
|
||||
|
||||
const enterZones = new Set(zonesByNewPosition);
|
||||
const leaveZones = new Set(zonesByOldPosition);
|
||||
|
||||
enterZones.forEach((zone) => {
|
||||
if (leaveZones.has(zone)) {
|
||||
leaveZones.delete(zone);
|
||||
enterZones.delete(zone);
|
||||
}
|
||||
});
|
||||
|
||||
if (enterZones.size > 0) {
|
||||
const zonesArray = Array.from(enterZones);
|
||||
for (const callback of this.enterZoneCallbacks) {
|
||||
callback(zonesArray, zonesByNewPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (leaveZones.size > 0) {
|
||||
const zonesArray = Array.from(leaveZones);
|
||||
for (const callback of this.leaveZoneCallbacks) {
|
||||
callback(zonesArray, zonesByNewPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentProperties(): Map<string, string | boolean | number> {
|
||||
return this.lastProperties;
|
||||
}
|
||||
|
@ -251,6 +336,20 @@ export class GameMap {
|
|||
this.leaveLayerCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback called when the user moves inside another zone.
|
||||
*/
|
||||
public onEnterZone(callback: zoneChangeCallback) {
|
||||
this.enterZoneCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback called when the user moves outside another zone.
|
||||
*/
|
||||
public onLeaveZone(callback: zoneChangeCallback) {
|
||||
this.leaveZoneCallbacks.push(callback);
|
||||
}
|
||||
|
||||
public findLayer(layerName: string): ITiledMapLayer | undefined {
|
||||
return this.flatLayers.find((layer) => layer.name === layerName);
|
||||
}
|
||||
|
@ -362,4 +461,17 @@ export class GameMap {
|
|||
this.trigger(oldPropName, oldPropValue, undefined, emptyProps);
|
||||
}
|
||||
}
|
||||
|
||||
private getObjectsFromLayers(layers: ITiledMapLayer[]): ITiledMapObject[] {
|
||||
const objects: ITiledMapObject[] = [];
|
||||
|
||||
const objectLayers = layers.filter((layer) => layer.type === "objectgroup");
|
||||
for (const objectLayer of objectLayers) {
|
||||
if (objectLayer.type === "objectgroup") {
|
||||
objects.push(...objectLayer.objects);
|
||||
}
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
|||
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import { Box, ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
|
@ -65,6 +65,7 @@ import type { ActionableItem } from "../Items/ActionableItem";
|
|||
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
||||
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
||||
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
||||
import { CameraManager } from "./CameraManager";
|
||||
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||
import type { Character } from "../Entity/Character";
|
||||
|
||||
|
@ -196,6 +197,7 @@ export class GameScene extends DirtyScene {
|
|||
private pinchManager: PinchManager | undefined;
|
||||
private mapTransitioning: boolean = false; //used to prevent transitions happening at the same time.
|
||||
private emoteManager!: EmoteManager;
|
||||
private cameraManager!: CameraManager;
|
||||
private preloading: boolean = true;
|
||||
private startPositionCalculator!: StartPositionCalculator;
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
|
@ -571,7 +573,13 @@ export class GameScene extends DirtyScene {
|
|||
this.createCurrentPlayer();
|
||||
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
|
||||
|
||||
this.initCamera();
|
||||
this.cameraManager = new CameraManager(
|
||||
this,
|
||||
{ x: this.Map.widthInPixels, y: this.Map.heightInPixels },
|
||||
waScaleManager
|
||||
);
|
||||
biggestAvailableAreaStore.recompute();
|
||||
this.cameraManager.startFollow(this.CurrentPlayer);
|
||||
|
||||
this.animatedTiles.init(this.Map);
|
||||
this.events.on("tileanimationupdate", () => (this.dirty = true));
|
||||
|
@ -612,7 +620,7 @@ export class GameScene extends DirtyScene {
|
|||
|
||||
// From now, this game scene will be notified of reposition events
|
||||
this.biggestAvailableAreaStoreUnsubscribe = biggestAvailableAreaStore.subscribe((box) =>
|
||||
this.updateCameraOffset(box)
|
||||
this.cameraManager.updateCameraOffset(box)
|
||||
);
|
||||
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
|
@ -665,7 +673,7 @@ export class GameScene extends DirtyScene {
|
|||
* Initializes the connection to Pusher.
|
||||
*/
|
||||
private connect(): void {
|
||||
const camera = this.cameras.main;
|
||||
const camera = this.cameraManager.getCamera();
|
||||
|
||||
connectionManager
|
||||
.connectToRoomSocket(
|
||||
|
@ -799,6 +807,42 @@ export class GameScene extends DirtyScene {
|
|||
iframeListener.sendLeaveLayerEvent(layer.name);
|
||||
});
|
||||
});
|
||||
|
||||
this.gameMap.onEnterZone((zones) => {
|
||||
for (const zone of zones) {
|
||||
const focusable = zone.properties?.find((property) => property.name === "focusable");
|
||||
if (focusable && focusable.value === true) {
|
||||
const zoomMargin = zone.properties?.find((property) => property.name === "zoom_margin");
|
||||
this.cameraManager.enterFocusMode(
|
||||
zone,
|
||||
zoomMargin ? Math.max(0, Number(zoomMargin.value)) : undefined
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zones.forEach((zone) => {
|
||||
iframeListener.sendEnterZoneEvent(zone.name);
|
||||
});
|
||||
});
|
||||
|
||||
this.gameMap.onLeaveZone((zones) => {
|
||||
for (const zone of zones) {
|
||||
const focusable = zone.properties?.find((property) => property.name === "focusable");
|
||||
if (focusable && focusable.value === true) {
|
||||
this.cameraManager.leaveFocusMode(this.CurrentPlayer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
zones.forEach((zone) => {
|
||||
iframeListener.sendLeaveZoneEvent(zone.name);
|
||||
});
|
||||
});
|
||||
|
||||
// this.gameMap.onLeaveLayer((layers) => {
|
||||
// layers.forEach((layer) => {
|
||||
// iframeListener.sendLeaveLayerEvent(layer.name);
|
||||
// });
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1187,6 +1231,7 @@ ${escapedMessage}
|
|||
roomId: this.roomUrl,
|
||||
tags: this.connection ? this.connection.getAllTags() : [],
|
||||
variables: this.sharedVariablesManager.variables,
|
||||
userRoomToken: this.connection ? this.connection.userRoomToken : "",
|
||||
};
|
||||
});
|
||||
this.iframeSubscriptionList.push(
|
||||
|
@ -1389,6 +1434,7 @@ ${escapedMessage}
|
|||
this.userInputManager.destroy();
|
||||
this.pinchManager?.destroy();
|
||||
this.emoteManager.destroy();
|
||||
this.cameraManager.destroy();
|
||||
this.peerStoreUnsubscribe();
|
||||
this.emoteUnsubscribe();
|
||||
this.emoteMenuUnsubscribe();
|
||||
|
@ -1478,13 +1524,6 @@ ${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);
|
||||
biggestAvailableAreaStore.recompute();
|
||||
}
|
||||
|
||||
createCollisionWithPlayer() {
|
||||
//add collision layer
|
||||
for (const phaserLayer of this.gameMap.phaserLayers) {
|
||||
|
@ -1896,23 +1935,6 @@ ${escapedMessage}
|
|||
biggestAvailableAreaStore.recompute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(array: Box): void {
|
||||
const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
|
||||
const 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:
|
||||
|
||||
this.cameras.main.setFollowOffset(
|
||||
((xCenter - game.offsetWidth / 2) * window.devicePixelRatio) / this.scale.zoom,
|
||||
((yCenter - game.offsetHeight / 2) * window.devicePixelRatio) / this.scale.zoom
|
||||
);
|
||||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(
|
||||
|
@ -1981,6 +2003,9 @@ ${escapedMessage}
|
|||
}
|
||||
|
||||
zoomByFactor(zoomFactor: number) {
|
||||
if (this.cameraManager.isCameraLocked()) {
|
||||
return;
|
||||
}
|
||||
waScaleManager.zoomModifier *= zoomFactor;
|
||||
biggestAvailableAreaStore.recompute();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap";
|
||||
import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
|
||||
interface Variable {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as Phaser from "phaser";
|
||||
import { Scene } from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import type { ITiledMapObject } from "../../Map/ITiledMap";
|
||||
import type { ItemFactoryInterface } from "../ItemFactoryInterface";
|
||||
|
|
|
@ -94,7 +94,7 @@ export class HdpiManager {
|
|||
/**
|
||||
* We only accept integer but we make an exception for 1.5
|
||||
*/
|
||||
private getOptimalZoomLevel(realPixelNumber: number): number {
|
||||
public getOptimalZoomLevel(realPixelNumber: number): number {
|
||||
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
|
||||
if (1.5 <= result && result < 2) {
|
||||
return 1.5;
|
||||
|
|
|
@ -5,13 +5,15 @@ import type { Game } from "../Game/Game";
|
|||
import { ResizableScene } from "../Login/ResizableScene";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
|
||||
class WaScaleManager {
|
||||
export class WaScaleManager {
|
||||
private hdpiManager: HdpiManager;
|
||||
private scaleManager!: ScaleManager;
|
||||
private game!: Game;
|
||||
private actualZoom: number = 1;
|
||||
private _saveZoom: number = 1;
|
||||
|
||||
private focusTarget?: { x: number; y: number; width: number; height: number };
|
||||
|
||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||
}
|
||||
|
@ -23,18 +25,14 @@ class WaScaleManager {
|
|||
|
||||
public applyNewSize() {
|
||||
const { width, height } = coWebsiteManager.getGameSize();
|
||||
|
||||
let devicePixelRatio = 1;
|
||||
if (window.devicePixelRatio) {
|
||||
devicePixelRatio = window.devicePixelRatio;
|
||||
}
|
||||
|
||||
const devicePixelRatio = window.devicePixelRatio ?? 1;
|
||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({
|
||||
width: width * devicePixelRatio,
|
||||
height: height * devicePixelRatio,
|
||||
});
|
||||
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||
|
||||
|
@ -59,6 +57,34 @@ class WaScaleManager {
|
|||
this.game.markDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this in case of resizing while focusing on something
|
||||
*/
|
||||
public refreshFocusOnTarget(): void {
|
||||
if (!this.focusTarget) {
|
||||
return;
|
||||
}
|
||||
this.zoomModifier = this.getTargetZoomModifierFor(this.focusTarget.width, this.focusTarget.height);
|
||||
this.game.events.emit("wa-scale-manager:refresh-focus-on-target", this.focusTarget);
|
||||
}
|
||||
|
||||
public setFocusTarget(targetDimensions?: { x: number; y: number; width: number; height: number }): void {
|
||||
this.focusTarget = targetDimensions;
|
||||
}
|
||||
|
||||
public getTargetZoomModifierFor(viewportWidth: number, viewportHeight: number) {
|
||||
const { width: gameWidth, height: gameHeight } = coWebsiteManager.getGameSize();
|
||||
const devicePixelRatio = window.devicePixelRatio ?? 1;
|
||||
|
||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({
|
||||
width: gameWidth * devicePixelRatio,
|
||||
height: gameHeight * devicePixelRatio,
|
||||
});
|
||||
const desiredZoom = Math.min(realSize.width / viewportWidth, realSize.height / viewportHeight);
|
||||
const realPixelNumber = gameWidth * devicePixelRatio * gameHeight * devicePixelRatio;
|
||||
return desiredZoom / (this.hdpiManager.getOptimalZoomLevel(realPixelNumber) || 1);
|
||||
}
|
||||
|
||||
public get zoomModifier(): number {
|
||||
return this.hdpiManager.zoomModifier;
|
||||
}
|
||||
|
@ -72,6 +98,10 @@ class WaScaleManager {
|
|||
this._saveZoom = this.hdpiManager.zoomModifier;
|
||||
}
|
||||
|
||||
public getSaveZoom(): number {
|
||||
return this._saveZoom;
|
||||
}
|
||||
|
||||
public restoreZoom(): void {
|
||||
this.hdpiManager.zoomModifier = this._saveZoom;
|
||||
this.applyNewSize();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue