Merge pull request #1616 from Waclaw-I/feature-camera-zone-focus

Focusable zones
This commit is contained in:
David Négrier 2021-12-14 15:43:45 +01:00 committed by GitHub
commit 0a705eadf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 987 additions and 47 deletions

View file

@ -0,0 +1,11 @@
import * as tg from "generic-type-guard";
export const isChangeZoneEvent = new tg.IsInterface()
.withProperties({
name: tg.isString,
})
.get();
/**
* A message sent from the game to the iFrame when a user enters or leaves a zone.
*/
export type ChangeZoneEvent = tg.GuardedType<typeof isChangeZoneEvent>;

View file

@ -28,6 +28,7 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
export interface TypedMessageEvent<T> extends MessageEvent {
data: T;
@ -76,6 +77,8 @@ export interface IframeResponseEventMap {
leaveEvent: EnterLeaveEvent;
enterLayerEvent: ChangeLayerEvent;
leaveLayerEvent: ChangeLayerEvent;
enterZoneEvent: ChangeZoneEvent;
leaveZoneEvent: ChangeZoneEvent;
buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent;
menuItemClicked: MenuItemClickedEvent;

View file

@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"],
@ -414,6 +415,24 @@ class IframeListener {
});
}
sendEnterZoneEvent(zoneName: string) {
this.postMessage({
type: "enterZoneEvent",
data: {
name: zoneName,
} as ChangeZoneEvent,
});
}
sendLeaveZoneEvent(zoneName: string) {
this.postMessage({
type: "leaveZoneEvent",
data: {
name: zoneName,
} as ChangeZoneEvent,
});
}
hasPlayerMoved(event: HasPlayerMovedEvent) {
if (this.sendPlayerMove) {
this.postMessage({

View 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);
}
);
}
}

View file

@ -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;
}
}

View file

@ -60,6 +60,7 @@ import { PinchManager } from "../UserInput/PinchManager";
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
import { waScaleManager } from "../Services/WaScaleManager";
import { EmoteManager } from "./EmoteManager";
import { CameraManager } from "./CameraManager";
import EVENT_TYPE = Phaser.Scenes.Events;
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
@ -198,6 +199,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;
@ -550,7 +552,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));
@ -591,7 +599,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();
@ -644,7 +652,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(
@ -779,6 +787,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);
// });
// });
});
}
@ -1370,6 +1414,7 @@ ${escapedMessage}
this.userInputManager.destroy();
this.pinchManager?.destroy();
this.emoteManager.destroy();
this.cameraManager.destroy();
this.peerStoreUnsubscribe();
this.emoteUnsubscribe();
this.emoteMenuUnsubscribe();
@ -1459,13 +1504,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) {
@ -1857,23 +1895,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(
@ -1942,6 +1963,9 @@ ${escapedMessage}
}
zoomByFactor(zoomFactor: number) {
if (this.cameraManager.isCameraLocked()) {
return;
}
waScaleManager.zoomModifier *= zoomFactor;
biggestAvailableAreaStore.recompute();
}

View file

@ -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 {

View file

@ -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";

View file

@ -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;

View file

@ -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();

View file

@ -0,0 +1,25 @@
export class MathUtils {
/**
*
* @param p Position to check.
* @param r Rectangle to check the overlap against.
* @returns true is overlapping
*/
public static isOverlappingWithRectangle(
p: { x: number; y: number },
r: { x: number; y: number; width: number; height: number }
): boolean {
return this.isBetween(p.x, r.x, r.x + r.width) && this.isBetween(p.y, r.y, r.y + r.height);
}
/**
*
* @param value Value to check
* @param min inclusive min value
* @param max inclusive max value
* @returns true if value is in <min, max>
*/
public static isBetween(value: number, min: number, max: number): boolean {
return value >= min && value <= max;
}
}

View file

@ -642,6 +642,7 @@ class CoWebsiteManager {
private fire(): void {
this._onResize.next();
waScaleManager.applyNewSize();
waScaleManager.refreshFocusOnTarget();
}
private fullscreen(): void {

View file

@ -144,10 +144,12 @@ window.addEventListener("resize", function (event) {
coWebsiteManager.resetStyleMain();
waScaleManager.applyNewSize();
waScaleManager.refreshFocusOnTarget();
});
coWebsiteManager.onResize.subscribe(() => {
waScaleManager.applyNewSize();
waScaleManager.refreshFocusOnTarget();
});
iframeListener.init();

View file

@ -21,3 +21,34 @@ export interface IVirtualJoystick extends Phaser.GameObjects.GameObject {
visible: boolean;
createCursorKeys: () => CursorKeys;
}
export enum Easing {
Linear = "Linear",
QuadEaseIn = "Quad.easeIn",
CubicEaseIn = "Cubic.easeIn",
QuartEaseIn = "Quart.easeIn",
QuintEaseIn = "Quint.easeIn",
SineEaseIn = "Sine.easeIn",
ExpoEaseIn = "Expo.easeIn",
CircEaseIn = "Circ.easeIn",
BackEaseIn = "Back.easeIn",
BounceEaseIn = "Bounce.easeIn",
QuadEaseOut = "Quad.easeOut",
CubicEaseOut = "Cubic.easeOut",
QuartEaseOut = "Quart.easeOut",
QuintEaseOut = "Quint.easeOut",
SineEaseOut = "Sine.easeOut",
ExpoEaseOut = "Expo.easeOut",
CircEaseOut = "Circ.easeOut",
BackEaseOut = "Back.easeOut",
BounceEaseOut = "Bounce.easeOut",
QuadEaseInOut = "Quad.easeInOut",
CubicEaseInOut = "Cubic.easeInOut",
QuartEaseInOut = "Quart.easeInOut",
QuintEaseInOut = "Quint.easeInOut",
SineEaseInOut = "Sine.easeInOut",
ExpoEaseInOut = "Expo.easeInOut",
CircEaseInOut = "Circ.easeInOut",
BackEaseInOut = "Back.easeInOut",
BounceEaseInOut = "Bounce.easeInOut",
}