From 89cd292527e711abe26193b8d822dc76ab80d5d8 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 15 Oct 2021 17:01:38 +0200 Subject: [PATCH 01/29] Allows to read and write "Player properties" from LocalStorage --- front/src/Api/Events/IframeEvent.ts | 11 ++++++++++- front/src/Api/Events/PlayerPropertyEvent.ts | 13 +++++++++++++ front/src/Api/iframe/player.ts | 17 ++++++++++++++++- front/src/Connexion/LocalUserStore.ts | 9 ++++++++- front/src/Phaser/Game/GameScene.ts | 13 +++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 front/src/Api/Events/PlayerPropertyEvent.ts diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 081008c4..9c491d3a 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -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 { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -59,7 +60,7 @@ export type IframeEventMap = { registerMenu: MenuRegisterEvent; unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact + modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact; }; export interface IframeEvent { type: T; @@ -149,6 +150,14 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, + getPlayerProperty: { + query: tg.isString, + answer: isPlayerPropertyEvent, + }, + setPlayerProperty: { + query: isPlayerPropertyEvent, + answer: tg.isUndefined, + }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/PlayerPropertyEvent.ts b/front/src/Api/Events/PlayerPropertyEvent.ts new file mode 100644 index 00000000..fe85d9ea --- /dev/null +++ b/front/src/Api/Events/PlayerPropertyEvent.ts @@ -0,0 +1,13 @@ +import * as tg from "generic-type-guard"; + +export const isPlayerPropertyEvent = new tg.IsInterface() + .withProperties({ + propertyName: tg.isString, + propertyValue: tg.isUnknown, + }) + .get(); + +/** + * A message sent from the iFrame to set player-related properties. + */ +export type PlayerPropertyEvent = tg.GuardedType; diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 078a1926..f97705f0 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -1,8 +1,9 @@ -import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent"; import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; +import type { PlayerPropertyEvent } from "../Events/PlayerPropertyEvent"; const moveStream = new Subject(); @@ -67,6 +68,20 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return queryWorkadventure({ + type: "getPlayerProperty", + data: name, + }); + } + + setPlayerProperty(property: PlayerPropertyEvent) { + queryWorkadventure({ + type: "setPlayerProperty", + data: property, + }).catch((e) => console.error(e)); + } } export default new WorkadventurePlayerCommands(); diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index ac57c3c7..49663346 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -20,8 +20,8 @@ const nonce = "nonce"; const notification = "notificationPermission"; const code = "code"; const cameraSetup = "cameraSetup"; - const cacheAPIIndex = "workavdenture-cache"; +const userProperties = "user-properties"; class LocalUserStore { saveUser(localUser: LocalUser) { @@ -204,6 +204,13 @@ class LocalUserStore { const cameraSetupValues = localStorage.getItem(cameraSetup); return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; } + getUserProperty(name: string): string | null { + return localStorage.getItem(userProperties + "_" + name); + } + + setUserProperty(name: string, value: string): void { + localStorage.setItem(userProperties + "_" + name, value); + } } export const localUserStore = new LocalUserStore(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d9bb8186..5a1407a0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1153,6 +1153,19 @@ ${escapedMessage} }; }); + //TODO : move Player Properties related-code + iframeListener.registerAnswerer("setPlayerProperty", (event) => { + localUserStore.setUserProperty(event.propertyName, event.propertyValue as string); + }); + + iframeListener.registerAnswerer("getPlayerProperty", (event) => { + return { + propertyName: event, + propertyValue: localUserStore.getUserProperty(event), + }; + }); + //END TODO + iframeListener.registerAnswerer("getState", async () => { // The sharedVariablesManager is not instantiated before the connection is established. So we need to wait // for the connection to send back the answer. From bf69b55e99468ef5fd899151a2269c8e29d03028 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 25 Oct 2021 14:43:36 +0200 Subject: [PATCH 02/29] Creates player state and uses it to get and set values from local storage --- front/src/Api/Events/GameStateEvent.ts | 1 + front/src/Api/Events/IframeEvent.ts | 8 -- front/src/Api/Events/PlayerPropertyEvent.ts | 13 -- front/src/Api/Events/SetVariableEvent.ts | 1 + front/src/Api/iframe/player.ts | 18 +-- front/src/Api/iframe/state.ts | 118 ++++++++++-------- front/src/Connexion/LocalUserStore.ts | 22 +++- front/src/Phaser/Game/GameScene.ts | 31 +++-- .../src/Phaser/Game/SharedVariablesManager.ts | 75 ++++++----- front/src/iframe_api.ts | 25 ++-- 10 files changed, 153 insertions(+), 159 deletions(-) delete mode 100644 front/src/Api/Events/PlayerPropertyEvent.ts diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 112c2880..96435f65 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -9,6 +9,7 @@ export const isGameStateEvent = new tg.IsInterface() startLayerName: tg.isUnion(tg.isString, tg.isNull), tags: tg.isArray(tg.isString), variables: tg.isObject, + playerVariables: tg.isObject, }) .get(); /** diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 9c491d3a..91280a0a 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -150,14 +150,6 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, - getPlayerProperty: { - query: tg.isString, - answer: isPlayerPropertyEvent, - }, - setPlayerProperty: { - query: isPlayerPropertyEvent, - answer: tg.isUndefined, - }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/PlayerPropertyEvent.ts b/front/src/Api/Events/PlayerPropertyEvent.ts deleted file mode 100644 index fe85d9ea..00000000 --- a/front/src/Api/Events/PlayerPropertyEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as tg from "generic-type-guard"; - -export const isPlayerPropertyEvent = new tg.IsInterface() - .withProperties({ - propertyName: tg.isString, - propertyValue: tg.isUnknown, - }) - .get(); - -/** - * A message sent from the iFrame to set player-related properties. - */ -export type PlayerPropertyEvent = tg.GuardedType; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 3e2303b3..80ac6f6e 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface() .withProperties({ key: tg.isString, value: tg.isUnknown, + target: tg.isSingletonStringUnion("global", "player"), }) .get(); /** diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index f97705f0..e25e0b9d 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -3,7 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; -import type { PlayerPropertyEvent } from "../Events/PlayerPropertyEvent"; +import { createState } from "./state"; const moveStream = new Subject(); @@ -26,6 +26,8 @@ export const setUuid = (_uuid: string | undefined) => { }; export class WorkadventurePlayerCommands extends IframeApiContribution { + readonly state = createState("player"); + callbacks = [ apiCallback({ type: "hasPlayerMoved", @@ -68,20 +70,6 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { - return queryWorkadventure({ - type: "getPlayerProperty", - data: name, - }); - } - - setPlayerProperty(property: PlayerPropertyEvent) { - queryWorkadventure({ - type: "setPlayerProperty", - data: property, - }).catch((e) => console.error(e)); - } } export default new WorkadventurePlayerCommands(); diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index a875f3e0..7021b251 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -8,93 +8,101 @@ import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; -const setVariableResolvers = new Subject(); -const variables = new Map(); -const variableSubscribers = new Map>(); - -export const initVariables = (_variables: Map): void => { - for (const [name, value] of _variables.entries()) { - // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. - if (!variables.has(name)) { - variables.set(name, value); - } - } -}; - -setVariableResolvers.subscribe((event) => { - const oldValue = variables.get(event.key); - // If we are setting the same value, no need to do anything. - // No need to do this check since it is already performed in SharedVariablesManager - /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { - return; - }*/ - - variables.set(event.key, event.value); - const subject = variableSubscribers.get(event.key); - if (subject !== undefined) { - subject.next(event.value); - } -}); - export class WorkadventureStateCommands extends IframeApiContribution { + private setVariableResolvers = new Subject(); + private variables = new Map(); + private variableSubscribers = new Map>(); + + constructor(private target: "global" | "player") { + super(); + + this.setVariableResolvers.subscribe((event) => { + const oldValue = this.variables.get(event.key); + // If we are setting the same value, no need to do anything. + // No need to do this check since it is already performed in SharedVariablesManager + /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { + return; + }*/ + + this.variables.set(event.key, event.value); + const subject = this.variableSubscribers.get(event.key); + if (subject !== undefined) { + subject.next(event.value); + } + }); + } + callbacks = [ apiCallback({ type: "setVariable", typeChecker: isSetVariableEvent, callback: (payloadData) => { - setVariableResolvers.next(payloadData); + if (payloadData.target === this.target) { + this.setVariableResolvers.next(payloadData); + } }, }), ]; + // TODO: see how we can remove this method from types exposed to WA.state object + initVariables(_variables: Map): void { + for (const [name, value] of _variables.entries()) { + // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. + if (!this.variables.has(name)) { + this.variables.set(name, value); + } + } + } + saveVariable(key: string, value: unknown): Promise { - variables.set(key, value); + this.variables.set(key, value); return queryWorkadventure({ type: "setVariable", data: { key, value, + target: this.target, }, }); } loadVariable(key: string): unknown { - return variables.get(key); + return this.variables.get(key); } hasVariable(key: string): boolean { - return variables.has(key); + return this.variables.has(key); } onVariableChange(key: string): Observable { - let subject = variableSubscribers.get(key); + let subject = this.variableSubscribers.get(key); if (subject === undefined) { subject = new Subject(); - variableSubscribers.set(key, subject); + this.variableSubscribers.set(key, subject); } return subject.asObservable(); } } -const proxyCommand = new Proxy(new WorkadventureStateCommands(), { - get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { - if (p in target) { - return Reflect.get(target, p, receiver); - } - return target.loadVariable(p.toString()); - }, - set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { - // Note: when using "set", there is no way to wait, so we ignore the return of the promise. - // User must use WA.state.saveVariable to have error message. - target.saveVariable(p.toString(), value); - return true; - }, - has(target: WorkadventureStateCommands, p: PropertyKey): boolean { - if (p in target) { +export function createState(target: "global" | "player"): WorkadventureStateCommands & { [key: string]: unknown } { + return new Proxy(new WorkadventureStateCommands(target), { + get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { + if (p in target) { + return Reflect.get(target, p, receiver); + } + return target.loadVariable(p.toString()); + }, + set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { + // Note: when using "set", there is no way to wait, so we ignore the return of the promise. + // User must use WA.state.saveVariable to have error message. + target.saveVariable(p.toString(), value); return true; - } - return target.hasVariable(p.toString()); - }, -}) as WorkadventureStateCommands & { [key: string]: unknown }; - -export default proxyCommand; + }, + has(target: WorkadventureStateCommands, p: PropertyKey): boolean { + if (p in target) { + return true; + } + return target.hasVariable(p.toString()); + }, + }) as WorkadventureStateCommands & { [key: string]: unknown }; +} diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 49663346..c9497dba 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -204,12 +204,26 @@ class LocalUserStore { const cameraSetupValues = localStorage.getItem(cameraSetup); return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; } - getUserProperty(name: string): string | null { - return localStorage.getItem(userProperties + "_" + name); + + getAllUserProperties(): Map { + const result = new Map(); + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + if (key.startsWith(userProperties + "_")) { + const value = localStorage.getItem(key); + if (value) { + const userKey = key.substr((userProperties + "_").length); + result.set(userKey, JSON.parse(value)); + } + } + } + } + return result; } - setUserProperty(name: string, value: string): void { - localStorage.setItem(userProperties + "_" + name, value); + setUserProperty(name: string, value: unknown): void { + localStorage.setItem(userProperties + "_" + name, JSON.stringify(value)); } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 5a1407a0..955b1ee3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1153,19 +1153,6 @@ ${escapedMessage} }; }); - //TODO : move Player Properties related-code - iframeListener.registerAnswerer("setPlayerProperty", (event) => { - localUserStore.setUserProperty(event.propertyName, event.propertyValue as string); - }); - - iframeListener.registerAnswerer("getPlayerProperty", (event) => { - return { - propertyName: event, - propertyValue: localUserStore.getUserProperty(event), - }; - }); - //END TODO - iframeListener.registerAnswerer("getState", async () => { // The sharedVariablesManager is not instantiated before the connection is established. So we need to wait // for the connection to send back the answer. @@ -1178,6 +1165,7 @@ ${escapedMessage} roomId: this.roomUrl, tags: this.connection ? this.connection.getAllTags() : [], variables: this.sharedVariablesManager.variables, + playerVariables: localUserStore.getAllUserProperties(), }; }); this.iframeSubscriptionList.push( @@ -1267,6 +1255,22 @@ ${escapedMessage} }) ); + iframeListener.registerAnswerer("setVariable", (event, source) => { + switch (event.target) { + case "global": { + this.sharedVariablesManager.setVariable(event, source); + break; + } + case "player": { + localUserStore.setUserProperty(event.key, event.value); + break; + } + default: { + const _exhaustiveCheck: never = event.target; + } + } + }); + iframeListener.registerAnswerer("removeActionMessage", (message) => { layoutManagerActionStore.removeAction(message.uuid); }); @@ -1391,6 +1395,7 @@ ${escapedMessage} iframeListener.unregisterAnswerer("removeActionMessage"); iframeListener.unregisterAnswerer("openCoWebsite"); iframeListener.unregisterAnswerer("getCoWebsites"); + iframeListener.unregisterAnswerer("setVariable"); this.sharedVariablesManager?.close(); this.embeddedWebsiteManager?.close(); diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 8f913765..8e4f3918 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -3,6 +3,7 @@ import { iframeListener } from "../../Api/IframeListener"; import type { GameMap } from "./GameMap"; import type { ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer } from "../Map/ITiledMap"; import { GameMapProperties } from "./GameMapProperties"; +import type { SetVariableEvent } from "../../Api/Events/SetVariableEvent"; interface Variable { defaultValue: unknown; @@ -48,51 +49,51 @@ export class SharedVariablesManager { iframeListener.setVariable({ key: name, value: value, + target: "global", }); }); + } - // When a variable is modified from an iFrame - iframeListener.registerAnswerer("setVariable", (event, source) => { - const key = event.key; + public setVariable(event: SetVariableEvent, source: MessageEventSource | null): void { + const key = event.key; - const object = this.variableObjects.get(key); + const object = this.variableObjects.get(key); - if (object === undefined) { - const errMsg = - 'A script is trying to modify variable "' + - key + - '" but this variable is not defined in the map.' + - 'There should be an object in the map whose name is "' + - key + - '" and whose type is "variable"'; - console.error(errMsg); - throw new Error(errMsg); - } + if (object === undefined) { + const errMsg = + 'A script is trying to modify variable "' + + key + + '" but this variable is not defined in the map.' + + 'There should be an object in the map whose name is "' + + key + + '" and whose type is "variable"'; + console.error(errMsg); + throw new Error(errMsg); + } - if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) { - const errMsg = - 'A script is trying to modify variable "' + - key + - '" but this variable is only writable for users with tag "' + - object.writableBy + - '".'; - console.error(errMsg); - throw new Error(errMsg); - } + if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) { + const errMsg = + 'A script is trying to modify variable "' + + key + + '" but this variable is only writable for users with tag "' + + object.writableBy + + '".'; + console.error(errMsg); + throw new Error(errMsg); + } - // Let's stop any propagation of the value we set is the same as the existing value. - if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) { - return; - } + // Let's stop any propagation of the value we set is the same as the existing value. + if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) { + return; + } - this._variables.set(key, event.value); + this._variables.set(key, event.value); - // Dispatch to the room connection. - this.roomConnection.emitSetVariableEvent(key, event.value); + // Dispatch to the room connection. + this.roomConnection.emitSetVariableEvent(key, event.value); - // Dispatch to other iframes - iframeListener.dispatchVariableToOtherIframes(key, event.value, source); - }); + // Dispatch to other iframes + iframeListener.dispatchVariableToOtherIframes(key, event.value, source); } private static findVariablesInMap(gameMap: GameMap): Map { @@ -164,10 +165,6 @@ export class SharedVariablesManager { return variable; } - public close(): void { - iframeListener.unregisterAnswerer("setVariable"); - } - get variables(): Map { return this._variables; } diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index dcd10fdc..54d5b3be 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -14,24 +14,27 @@ import controls from "./Api/iframe/controls"; import ui from "./Api/iframe/ui"; import sound from "./Api/iframe/sound"; import room, { setMapURL, setRoomId } from "./Api/iframe/room"; -import state, { initVariables } from "./Api/iframe/state"; +import { createState } from "./Api/iframe/state"; import player, { setPlayerName, setTags, setUuid } from "./Api/iframe/player"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; -import { answerPromises, queryWorkadventure, sendToWorkadventure } from "./Api/iframe/IframeApiContribution"; +import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; + +const globalState = createState("global"); // Notify WorkAdventure that we are ready to receive data const initPromise = queryWorkadventure({ type: "getState", data: undefined, -}).then((state) => { - setPlayerName(state.nickname); - setRoomId(state.roomId); - setMapURL(state.mapUrl); - setTags(state.tags); - setUuid(state.uuid); - initVariables(state.variables as Map); +}).then((gameState) => { + setPlayerName(gameState.nickname); + setRoomId(gameState.roomId); + setMapURL(gameState.mapUrl); + setTags(gameState.tags); + setUuid(gameState.uuid); + globalState.initVariables(gameState.variables as Map); + player.state.initVariables(gameState.playerVariables as Map); }); const wa = { @@ -42,7 +45,7 @@ const wa = { sound, room, player, - state, + state: globalState, onInit(): Promise { return initPromise; @@ -224,7 +227,5 @@ window.addEventListener( callback?.callback(payloadData); } } - - // ... } ); From c5f96a76ad6e4dc936bd5837abd55f812ee52468 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 25 Oct 2021 17:23:02 +0200 Subject: [PATCH 03/29] Adds documentation on player properties --- docs/maps/api-player.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 39a13d9e..1b52cce8 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -78,3 +78,24 @@ Example : ```javascript WA.player.onPlayerMove(console.log); ``` + +## Player specific properties +Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. + +Any value that is serializable in JSON can be stored. + +### Setting a property +A player property can be set simply by assigning a value. + +Example: +```javascript +WA.player.state.toto = "value" //will set the "toto" key to "value" +``` + +### Reading a property +A player property can be read by calling its key from the player's state. + +Example: +```javascript +WA.player.state.toto //will retrieve the property +``` From a22c2a09b81e3475d47760596da14502e2ede6a3 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 12:01:07 +0200 Subject: [PATCH 04/29] Uses the current player position rather than the starting one to position iframe --- front/src/Api/Events/IframeEvent.ts | 5 +++++ front/src/Api/Events/PlayerPosition.ts | 10 ++++++++++ front/src/Api/iframe/player.ts | 14 ++++++++++++++ front/src/Phaser/Game/GameScene.ts | 8 ++++++++ 4 files changed, 37 insertions(+) create mode 100644 front/src/Api/Events/PlayerPosition.ts diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 91280a0a..4a8ab570 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -29,6 +29,7 @@ import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/Trigg import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; +import { isPlayerPosition } from "./PlayerPosition"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -150,6 +151,10 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, + getPlayerPosition: { + query: tg.isUndefined, + answer: isPlayerPosition, + }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/PlayerPosition.ts b/front/src/Api/Events/PlayerPosition.ts new file mode 100644 index 00000000..54fac6fe --- /dev/null +++ b/front/src/Api/Events/PlayerPosition.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isPlayerPosition = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + }) + .get(); + +export type PlayerPosition = tg.GuardedType; diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index e25e0b9d..16c0d81d 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -70,6 +70,20 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + //TODO: type + return await queryWorkadventure({ + type: "getPlayerPosition", + data: undefined, + }); + } } +//TODO: move or delete +type Position = { + x: number; + y: number; +}; + export default new WorkadventurePlayerCommands(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 955b1ee3..b3dd7c83 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1274,6 +1274,14 @@ ${escapedMessage} iframeListener.registerAnswerer("removeActionMessage", (message) => { layoutManagerActionStore.removeAction(message.uuid); }); + + + iframeListener.registerAnswerer("getPlayerPosition", () => { + return { + x: this.CurrentPlayer.x, + y: this.CurrentPlayer.y, + }; + }); } private setPropertyLayer( From 58e36ebed239b24e51aa7086f9454ac615e5991d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:34:54 +0200 Subject: [PATCH 05/29] Update docs/maps/api-player.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David NĂ©grier --- docs/maps/api-player.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 1b52cce8..46017422 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -82,6 +82,9 @@ WA.player.onPlayerMove(console.log); ## Player specific properties Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. +{.alert.alert-info} +In the future, player-related variables will be stored on the WorkAdventure server if the current player is logged. + Any value that is serializable in JSON can be stored. ### Setting a property From 926fcf9d636da3ff0417a3610c48de6f007e1fc1 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 18:53:55 +0200 Subject: [PATCH 06/29] Cleans forgotten useless commentaries --- front/src/Api/iframe/player.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 16c0d81d..4cffb11b 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -72,7 +72,6 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { - //TODO: type return await queryWorkadventure({ type: "getPlayerPosition", data: undefined, @@ -80,8 +79,7 @@ export class WorkadventurePlayerCommands extends IframeApiContribution Date: Fri, 29 Oct 2021 18:56:04 +0200 Subject: [PATCH 07/29] Updates documentation --- docs/maps/api-player.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 46017422..b8ef612c 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -58,6 +58,22 @@ WA.onInit().then(() => { }) ``` +### Get the position of the player +``` +WA.player.getPosition(): Promise +``` +The player's current position is available using the `WA.player.getPosition()` function. + +{.alert.alert-info} +You need to wait for the end of the initialization before calling `WA.player.getPosition()` + +```typescript +WA.onInit().then(() => { + console.log('Tags: ', WA.player.getPosition()); +}) +``` + + ### Listen to player movement ``` WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; @@ -79,8 +95,8 @@ Example : WA.player.onPlayerMove(console.log); ``` -## Player specific properties -Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. +## Player specific variables +Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. Any value that is serializable in JSON can be stored. {.alert.alert-info} In the future, player-related variables will be stored on the WorkAdventure server if the current player is logged. @@ -95,10 +111,10 @@ Example: WA.player.state.toto = "value" //will set the "toto" key to "value" ``` -### Reading a property -A player property can be read by calling its key from the player's state. +### Reading a variable +A player variable can be read by calling its key from the player's state. Example: ```javascript -WA.player.state.toto //will retrieve the property +WA.player.state.toto //will retrieve the variable ``` From 353577b1d71e6bd76af8d32bd3607452f0ff91c8 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 19:27:14 +0200 Subject: [PATCH 08/29] Fixes deleting SharedVariablesManager's close() function --- front/src/Phaser/Game/SharedVariablesManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 8e4f3918..8e7f3119 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -165,6 +165,10 @@ export class SharedVariablesManager { return variable; } + public close(): void { + iframeListener.unregisterAnswerer("setVariable"); + } + get variables(): Map { return this._variables; } From 99dfd77600ea1ab653cf948d027ba8f8e1ef201c Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 10 Nov 2021 19:04:06 +0100 Subject: [PATCH 09/29] WIP API updates for tutorial and more --- front/src/Api/Events/EmbeddedWebsiteEvent.ts | 4 ++ front/src/Api/Events/IframeEvent.ts | 1 + front/src/Api/iframe/Room/EmbeddedWebsite.ts | 43 ++++++++++++++ front/src/Api/iframe/website.ts | 4 -- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 58 +++++++++++++++---- front/src/Phaser/Game/GameScene.ts | 4 +- .../website_in_map_script.html | 16 +++++ 7 files changed, 115 insertions(+), 15 deletions(-) diff --git a/front/src/Api/Events/EmbeddedWebsiteEvent.ts b/front/src/Api/Events/EmbeddedWebsiteEvent.ts index 42630be1..57c24853 100644 --- a/front/src/Api/Events/EmbeddedWebsiteEvent.ts +++ b/front/src/Api/Events/EmbeddedWebsiteEvent.ts @@ -22,6 +22,8 @@ export const isEmbeddedWebsiteEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); @@ -35,6 +37,8 @@ export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface() visible: tg.isBoolean, allowApi: tg.isBoolean, allow: tg.isString, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 4a8ab570..b8753a9c 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -195,6 +195,7 @@ export const isIframeQuery = (event: any): event is IframeQuery { console.log('CREATING NEW EMBEDDED IFRAME'); @@ -32,6 +34,8 @@ height: parseInt(heightField.value), }, visible: !!visibleField.value, + origin: originField.value, + scale: parseFloat(scaleField.value), }); }); @@ -65,6 +69,16 @@ const website = await WA.room.website.get('test'); website.visible = this.checked; }); + + originField.addEventListener('change', async function() { + const website = await WA.room.website.get('test'); + website.origin = this.value; + }); + + scaleField.addEventListener('change', async function() { + const website = await WA.room.website.get('test'); + website.scale = parseFloat(this.value); + }); }); }) @@ -76,6 +90,8 @@ width:
height:
URL:
Visible:
+Origin:
+Scale:
From 5b6a8ca4d794bd1f52f3de8fd5a424cdbdac7224 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 16:51:39 +0100 Subject: [PATCH 10/29] Adds the camera to available APIs with retrieving of the worldView --- docs/maps/api-player.md | 2 +- front/src/Api/Events/HasCameraMovedEvent.ts | 18 ++++++++++++ front/src/Api/Events/IframeEvent.ts | 3 ++ front/src/Api/IframeListener.ts | 13 +++++++++ front/src/Api/iframe/Room/EmbeddedWebsite.ts | 6 ++-- front/src/Api/iframe/camera.ts | 29 +++++++++++++++++++ .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +++-- front/src/Phaser/Game/GameScene.ts | 13 +++++++++ front/src/iframe_api.ts | 2 ++ 9 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 front/src/Api/Events/HasCameraMovedEvent.ts create mode 100644 front/src/Api/iframe/camera.ts diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index b8ef612c..631424a8 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -69,7 +69,7 @@ You need to wait for the end of the initialization before calling `WA.player.get ```typescript WA.onInit().then(() => { - console.log('Tags: ', WA.player.getPosition()); + console.log('Position: ', WA.player.getPosition()); }) ``` diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/HasCameraMovedEvent.ts new file mode 100644 index 00000000..23f85385 --- /dev/null +++ b/front/src/Api/Events/HasCameraMovedEvent.ts @@ -0,0 +1,18 @@ +import * as tg from "generic-type-guard"; + +export const isHasCameraMovedEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + }) + .get(); + +/** + * A message sent from the game to the iFrame to notify a movement from the camera. + */ + +export type HasCameraMovedEvent = tg.GuardedType; + +export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index b8753a9c..23731d7c 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -30,6 +30,7 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import { isPlayerPosition } from "./PlayerPosition"; +import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -50,6 +51,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; + onCameraMove: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -80,6 +82,7 @@ export interface IframeResponseEventMap { leaveLayerEvent: ChangeLayerEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; + hasCameraMoved: HasCameraMovedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index f30ce80c..4947bb03 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -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 { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -94,6 +95,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private sendCameraMove: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -225,6 +227,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; + } else if (payload.type == "onCameraMove") { + this.sendCameraMove = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -423,6 +427,15 @@ class IframeListener { } } + hasCameraMoved(event: HasCameraMovedEvent) { + if (this.sendCameraMove) { + this.postMessage({ + type: "hasCameraMoved", + data: event, + }); + } + } + sendButtonClickedEvent(popupId: number, buttonId: number): void { this.postMessage({ type: "buttonClickedEvent", diff --git a/front/src/Api/iframe/Room/EmbeddedWebsite.ts b/front/src/Api/iframe/Room/EmbeddedWebsite.ts index 1a2761bd..d9c2d986 100644 --- a/front/src/Api/iframe/Room/EmbeddedWebsite.ts +++ b/front/src/Api/iframe/Room/EmbeddedWebsite.ts @@ -13,7 +13,7 @@ export class EmbeddedWebsite { private _allowApi: boolean; private _position: Rectangle; private readonly origin: "map" | "player" | undefined; - private _scale: number | undefined; + private _scale: number; constructor(private config: CreateEmbeddedWebsiteEvent) { this.name = config.name; @@ -23,7 +23,7 @@ export class EmbeddedWebsite { this._allowApi = config.allowApi ?? false; this._position = config.position; this.origin = config.origin; - this._scale = config.scale; + this._scale = config.scale ?? 1; } public get url() { @@ -116,7 +116,7 @@ export class EmbeddedWebsite { }); } - public get scale() { + public get scale(): number { return this._scale; } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts new file mode 100644 index 00000000..e2fb258e --- /dev/null +++ b/front/src/Api/iframe/camera.ts @@ -0,0 +1,29 @@ +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Subject } from "rxjs"; +import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import { apiCallback } from "./registeredCallbacks"; +import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; + +const moveStream = new Subject(); + +export class WorkAdventureCameraCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: "hasCameraMoved", + typeChecker: isHasCameraMovedEvent, + callback: (payloadData) => { + moveStream.next(payloadData); + }, + }), + ]; + + onCameraMove(callback: HasCameraMovedEventCallback): void { + moveStream.subscribe(callback); + sendToWorkadventure({ + type: "onCameraMove", + data: null, + }); + } +} + +export default new WorkAdventureCameraCommands(); diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 99c4bf5f..36e6b305 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -30,6 +30,7 @@ export class EmbeddedWebsiteManager { height: rect["height"], }, origin: website.origin, + scale: website.scale, }; }); @@ -144,9 +145,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ + y, + width, + height,*/ allow, allowApi, visible, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ffbcf1db..2136a252 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -89,6 +89,8 @@ import { get } from "svelte/store"; import { contactPageStore } from "../../Stores/MenuStore"; import { GameMapProperties } from "./GameMapProperties"; import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile; +import Camera = Phaser.Cameras.Scene2D.Camera; +import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -750,6 +752,17 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); + //listen event to share the actual worldView when the camera is updated + this.cameras.main.on("followupdate", (camera: Camera) => { + const worldView: HasCameraMovedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + }; + iframeListener.hasCameraMoved(worldView); + }); + // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 54d5b3be..216096b1 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -20,6 +20,7 @@ import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; +import camera from "./Api/iframe/camera"; const globalState = createState("global"); @@ -45,6 +46,7 @@ const wa = { sound, room, player, + camera, state: globalState, onInit(): Promise { From f761858328cd3b328199e4817b2f016c60ab428c Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 17:39:45 +0100 Subject: [PATCH 11/29] Refactoring and documentation update --- docs/maps/api-camera.md | 23 +++++++++++++++++++ docs/maps/api-reference.md | 1 + front/src/Api/Events/IframeEvent.ts | 6 ++--- ...MovedEvent.ts => WasCameraUpdatedEvent.ts} | 6 ++--- front/src/Api/IframeListener.ts | 14 +++++------ front/src/Api/iframe/camera.ts | 14 +++++------ front/src/Phaser/Game/GameScene.ts | 6 ++--- 7 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 docs/maps/api-camera.md rename front/src/Api/Events/{HasCameraMovedEvent.ts => WasCameraUpdatedEvent.ts} (55%) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md new file mode 100644 index 00000000..b5b85b64 --- /dev/null +++ b/docs/maps/api-camera.md @@ -0,0 +1,23 @@ +{.section-title.accent.text-primary} +# API Camera functions Reference + +### Listen to the camera update + +``` +WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void +``` + +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. + +The event has the following attributes : +* **x (number):** coordinate X of the camera's world view (the area looked at by the camera). +* **y (number):** coordinate Y of the camera's world view. +* **width (number):** the width of the camera's world view. +* **height (number):** the height of the camera's world view. + +**callback:** the function that will be called when the camera is updated. + +Example : +```javascript +WA.camera.onCameraUpdate((worldView) => console.log(worldView)); +``` \ No newline at end of file diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index d044668f..a0869075 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -10,5 +10,6 @@ - [UI functions](api-ui.md) - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) +- [Camera functions](api-camera.md) - [List of deprecated functions](api-deprecated.md) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 23731d7c..ee38b7d6 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -30,7 +30,7 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import { isPlayerPosition } from "./PlayerPosition"; -import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -51,7 +51,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; - onCameraMove: undefined; + onCameraUpdate: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -82,7 +82,7 @@ export interface IframeResponseEventMap { leaveLayerEvent: ChangeLayerEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; - hasCameraMoved: HasCameraMovedEvent; + wasCameraUpdated: WasCameraUpdatedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts similarity index 55% rename from front/src/Api/Events/HasCameraMovedEvent.ts rename to front/src/Api/Events/WasCameraUpdatedEvent.ts index 23f85385..8f37753c 100644 --- a/front/src/Api/Events/HasCameraMovedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -1,6 +1,6 @@ import * as tg from "generic-type-guard"; -export const isHasCameraMovedEvent = new tg.IsInterface() +export const isWasCameraUpdatedEvent = new tg.IsInterface() .withProperties({ x: tg.isNumber, y: tg.isNumber, @@ -13,6 +13,6 @@ export const isHasCameraMovedEvent = new tg.IsInterface() * A message sent from the game to the iFrame to notify a movement from the camera. */ -export type HasCameraMovedEvent = tg.GuardedType; +export type WasCameraUpdatedEvent = tg.GuardedType; -export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; +export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 4947bb03..08b96a4e 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,7 +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 { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -95,7 +95,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraMove: boolean = false; + private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -227,8 +227,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if (payload.type == "onCameraMove") { - this.sendCameraMove = true; + } else if (payload.type == "onCameraUpdate") { + this.sendCameraUpdate = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -427,10 +427,10 @@ class IframeListener { } } - hasCameraMoved(event: HasCameraMovedEvent) { - if (this.sendCameraMove) { + sendCameraUpdated(event: WasCameraUpdatedEvent) { + if (this.sendCameraUpdate) { this.postMessage({ - type: "hasCameraMoved", + type: "wasCameraUpdated", data: event, }); } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index e2fb258e..4f62b94c 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -1,26 +1,26 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { Subject } from "rxjs"; -import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent"; import { apiCallback } from "./registeredCallbacks"; -import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; +import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; -const moveStream = new Subject(); +const moveStream = new Subject(); export class WorkAdventureCameraCommands extends IframeApiContribution { callbacks = [ apiCallback({ - type: "hasCameraMoved", - typeChecker: isHasCameraMovedEvent, + type: "wasCameraUpdated", + typeChecker: isWasCameraUpdatedEvent, callback: (payloadData) => { moveStream.next(payloadData); }, }), ]; - onCameraMove(callback: HasCameraMovedEventCallback): void { + onCameraUpdate(callback: WasCameraUpdatedEventCallback): void { moveStream.subscribe(callback); sendToWorkadventure({ - type: "onCameraMove", + type: "onCameraUpdate", data: null, }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2136a252..67373a51 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -90,7 +90,7 @@ import { contactPageStore } from "../../Stores/MenuStore"; import { GameMapProperties } from "./GameMapProperties"; import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile; import Camera = Phaser.Cameras.Scene2D.Camera; -import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -754,13 +754,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: HasCameraMovedEvent = { + const worldView: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.hasCameraMoved(worldView); + iframeListener.sendCameraUpdated(worldView); }); // Set up variables manager From 7eda8a45a710292a68d7d016ef6be86881a799d1 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 26 Nov 2021 14:46:02 +0100 Subject: [PATCH 12/29] Reverts adding scale to the camera updated event and uses it directly from the website manager --- front/src/Api/Events/IframeEvent.ts | 1 - .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 68 +++++++++---------- front/src/Phaser/Game/GameScene.ts | 5 +- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ee38b7d6..cf0b7e74 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -28,7 +28,6 @@ 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 { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import { isPlayerPosition } from "./PlayerPosition"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 36e6b305..d58d7eaf 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -26,8 +26,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: rect["width"], - height: rect["height"], + width: website.phaserObject.width, + height: website.phaserObject.height, }, origin: website.origin, scale: website.scale, @@ -111,14 +111,18 @@ export class EmbeddedWebsiteManager { website.phaserObject.y = embeddedWebsiteEvent.y; } if (embeddedWebsiteEvent?.width !== undefined) { - website.iframe.style.width = embeddedWebsiteEvent.width + "px"; + website.position.width = embeddedWebsiteEvent.width; + website.iframe.style.width = embeddedWebsiteEvent.width / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.height !== undefined) { - website.iframe.style.height = embeddedWebsiteEvent.height + "px"; + website.position.height = embeddedWebsiteEvent.height; + website.iframe.style.height = embeddedWebsiteEvent.height / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.scale !== undefined) { website.phaserObject.scale = embeddedWebsiteEvent.scale; + website.iframe.style.width = website.position.width / embeddedWebsiteEvent.scale + "px"; + website.iframe.style.height = website.position.height / embeddedWebsiteEvent.scale + "px"; } } ); @@ -145,9 +149,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ +y, +width, +height,*/ allow, allowApi, visible, @@ -173,49 +177,43 @@ export class EmbeddedWebsiteManager { const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString(); const iframe = document.createElement("iframe"); + const scale = embeddedWebsiteEvent.scale ?? 1; + iframe.src = absoluteUrl; iframe.tabIndex = -1; - iframe.style.width = embeddedWebsiteEvent.position.width + "px"; - iframe.style.height = embeddedWebsiteEvent.position.height + "px"; + iframe.style.width = embeddedWebsiteEvent.position.width / scale + "px"; + iframe.style.height = embeddedWebsiteEvent.position.height / scale + "px"; iframe.style.margin = "0"; iframe.style.padding = "0"; iframe.style.border = "none"; - let embeddedWebsite; - let domElement; + const domElement = new DOMElement( + this.gameScene, + embeddedWebsiteEvent.position.x, + embeddedWebsiteEvent.position.y, + iframe + ); + domElement.setOrigin(0, 0); + if (embeddedWebsiteEvent.scale) { + domElement.scale = embeddedWebsiteEvent.scale; + } + domElement.setVisible(visible); switch (embeddedWebsiteEvent.origin) { case "player": - domElement = new DOMElement( - this.gameScene, - embeddedWebsiteEvent.position.x, - embeddedWebsiteEvent.position.y, - iframe - ); - if (embeddedWebsiteEvent.scale) { - domElement.scale = embeddedWebsiteEvent.scale; - } this.gameScene.CurrentPlayer.add(domElement); - - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: domElement, - iframe: iframe, - }; - break; case "map": default: - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: this.gameScene.add - .dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe) - .setVisible(visible) - .setOrigin(0, 0), - iframe: iframe, - }; + this.gameScene.add.existing(domElement); } + const embeddedWebsite = { + ...embeddedWebsiteEvent, + phaserObject: domElement, + iframe: iframe, + }; + if (embeddedWebsiteEvent.allowApi) { iframeListener.registerIframe(iframe); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 67373a51..d5a32d86 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -754,13 +754,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: WasCameraUpdatedEvent = { + const cameraEvent: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.sendCameraUpdated(worldView); + iframeListener.sendCameraUpdated(cameraEvent); }); // Set up variables manager @@ -1290,7 +1290,6 @@ ${escapedMessage} layoutManagerActionStore.removeAction(message.uuid); }); - iframeListener.registerAnswerer("getPlayerPosition", () => { return { x: this.CurrentPlayer.x, From 4b4f5520db6316e79f440468da7bb463f7809c9d Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 6 Dec 2021 18:44:37 +0100 Subject: [PATCH 13/29] Corrects scale managing and camera event listening --- front/src/Api/Events/WasCameraUpdatedEvent.ts | 1 + front/src/Api/IframeListener.ts | 16 +++--- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +-- front/src/Phaser/Game/GameScene.ts | 51 ++++++++++++++----- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/front/src/Api/Events/WasCameraUpdatedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts index 8f37753c..34e39a84 100644 --- a/front/src/Api/Events/WasCameraUpdatedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -6,6 +6,7 @@ export const isWasCameraUpdatedEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + zoom: tg.isNumber, }) .get(); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 08b96a4e..8736865f 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -85,6 +85,9 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); + private readonly _trackCameraUpdateStream: Subject = new Subject(); + public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); public readonly setTilesStream = this._setTilesStream.asObservable(); @@ -95,7 +98,6 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -228,7 +230,7 @@ class IframeListener { } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; } else if (payload.type == "onCameraUpdate") { - this.sendCameraUpdate = true; + this._trackCameraUpdateStream.next(); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -428,12 +430,10 @@ class IframeListener { } sendCameraUpdated(event: WasCameraUpdatedEvent) { - if (this.sendCameraUpdate) { - this.postMessage({ - type: "wasCameraUpdated", - data: event, - }); - } + this.postMessage({ + type: "wasCameraUpdated", + data: event, + }); } sendButtonClickedEvent(popupId: number, buttonId: number): void { diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index d58d7eaf..387940c7 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -16,7 +16,8 @@ export class EmbeddedWebsiteManager { if (website === undefined) { throw new Error('Cannot find embedded website with name "' + name + '"'); } - const rect = website.iframe.getBoundingClientRect(); + + const scale = website.scale ?? 1; return { url: website.url, name: website.name, @@ -26,8 +27,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: website.phaserObject.width, - height: website.phaserObject.height, + width: website.phaserObject.width * scale, + height: website.phaserObject.height * scale, }, origin: website.origin, scale: website.scale, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d5a32d86..b488b247 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -206,6 +206,8 @@ export class GameScene extends DirtyScene { private objectsByType = new Map(); private embeddedWebsiteManager!: EmbeddedWebsiteManager; private loader: Loader; + private lastCameraEvent: WasCameraUpdatedEvent | undefined; + private firstCameraUpdateSent: boolean = false; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -752,17 +754,6 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); - //listen event to share the actual worldView when the camera is updated - this.cameras.main.on("followupdate", (camera: Camera) => { - const cameraEvent: WasCameraUpdatedEvent = { - x: camera.worldView.x, - y: camera.worldView.y, - width: camera.worldView.width, - height: camera.worldView.height, - }; - iframeListener.sendCameraUpdated(cameraEvent); - }); - // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, @@ -1045,9 +1036,33 @@ ${escapedMessage} ); this.iframeSubscriptionList.push( - iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { - const url = new URL(stopSoundEvent.url, this.MapUrlFile); - soundManager.stopSound(this.sound, url.toString()); + iframeListener.trackCameraUpdateStream.subscribe(() => { + if (!this.firstCameraUpdateSent) { + this.cameras.main.on("followupdate", (camera: Camera) => { + const cameraEvent: WasCameraUpdatedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + zoom: camera.scaleManager.zoom, + }; + if ( + this.lastCameraEvent?.x == cameraEvent.x && + this.lastCameraEvent?.y == cameraEvent.y && + this.lastCameraEvent?.width == cameraEvent.width && + this.lastCameraEvent?.height == cameraEvent.height && + this.lastCameraEvent?.zoom == cameraEvent.zoom + ) { + return; + } + + this.lastCameraEvent = cameraEvent; + iframeListener.sendCameraUpdated(cameraEvent); + this.firstCameraUpdateSent = true; + }); + + iframeListener.sendCameraUpdated(this.cameras.main); + } }) ); @@ -1110,6 +1125,12 @@ ${escapedMessage} }) ); + this.iframeSubscriptionList.push( + iframeListener.setPropertyStream.subscribe((setProperty) => { + this.setPropertyLayer(setProperty.layerName, setProperty.propertyName, setProperty.propertyValue); + }) + ); + iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => { if (!source) { throw new Error("Unknown query source"); @@ -1877,6 +1898,7 @@ ${escapedMessage} this.loader.resize(); } + private getObjectLayerData(objectName: string): ITiledMapObject | undefined { for (const layer of this.mapFile.layers) { if (layer.type === "objectgroup" && layer.name === "floorLayer") { @@ -1889,6 +1911,7 @@ ${escapedMessage} } return undefined; } + private reposition(): void { // Recompute camera offset if needed biggestAvailableAreaStore.recompute(); From 08824b70aac4511e68a099266352f55a72594a05 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 7 Dec 2021 18:47:40 +0100 Subject: [PATCH 14/29] Documentation --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index b5b85b64..7ae9ce2b 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From 27083843546f33622351ca1d77cc0e601f916bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:05:46 +0100 Subject: [PATCH 15/29] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David NĂ©grier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 7ae9ce2b..405de418 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -1,7 +1,7 @@ {.section-title.accent.text-primary} # API Camera functions Reference -### Listen to the camera update +### Listen to camera updates ``` WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void From 66da11f8544578318da092b070121e3789af08a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:07 +0100 Subject: [PATCH 16/29] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David NĂ©grier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 405de418..e0b5d7f7 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. +Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From 8a6ad40d6b5bd059d1b7e676a79a162457584196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:34 +0100 Subject: [PATCH 17/29] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David NĂ©grier --- front/src/Api/Events/IframeEvent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index cf0b7e74..8311db7b 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -197,7 +197,6 @@ export const isIframeQuery = (event: any): event is IframeQuery Date: Wed, 8 Dec 2021 10:14:31 +0100 Subject: [PATCH 18/29] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David NĂ©grier --- front/src/Api/Events/IframeEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 8311db7b..01ac2b5d 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -62,7 +62,7 @@ export type IframeEventMap = { registerMenu: MenuRegisterEvent; unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact; + modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact }; export interface IframeEvent { type: T; From 6871335e9b5924164df426d5a29e988cd8fb613c Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 8 Dec 2021 11:48:16 +0100 Subject: [PATCH 19/29] Documentation on type Position --- docs/maps/api-player.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 631424a8..ece94b07 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -64,6 +64,11 @@ WA.player.getPosition(): Promise ``` The player's current position is available using the `WA.player.getPosition()` function. +`Position` has the following attributes : +* **x (number) :** The coordinate x of the current player's position. +* **y (number) :** The coordinate y of the current player's position. + + {.alert.alert-info} You need to wait for the end of the initialization before calling `WA.player.getPosition()` From 5f26a39a5d5aec02efda99350f87a4c750343611 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 16 Dec 2021 13:41:28 +0100 Subject: [PATCH 20/29] added basic WA.camera commands --- front/src/Api/Events/CameraFocusOnEvent.ts | 15 ++++++++++++ .../src/Api/Events/CameraFollowPlayerEvent.ts | 11 +++++++++ .../src/Api/Events/CameraSetPositionEvent.ts | 15 ++++++++++++ front/src/Api/Events/IframeEvent.ts | 6 +++++ front/src/Api/IframeListener.ts | 18 +++++++++++++++ front/src/Api/iframe/camera.ts | 21 +++++++++++++++++ front/src/Api/iframe/chat.ts | 1 - front/src/Phaser/Game/CameraManager.ts | 6 ++--- front/src/Phaser/Game/GameScene.ts | 23 +++++++++++++++++++ 9 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 front/src/Api/Events/CameraFocusOnEvent.ts create mode 100644 front/src/Api/Events/CameraFollowPlayerEvent.ts create mode 100644 front/src/Api/Events/CameraSetPositionEvent.ts diff --git a/front/src/Api/Events/CameraFocusOnEvent.ts b/front/src/Api/Events/CameraFocusOnEvent.ts new file mode 100644 index 00000000..e6a1547e --- /dev/null +++ b/front/src/Api/Events/CameraFocusOnEvent.ts @@ -0,0 +1,15 @@ +import * as tg from "generic-type-guard"; + +export const isCameraFocusOnEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + smooth: tg.isBoolean, + }) + .get(); +/** + * A message sent from the iFrame to the game to set the camera focus on certain place. + */ +export type CameraFocusOnEvent = tg.GuardedType; diff --git a/front/src/Api/Events/CameraFollowPlayerEvent.ts b/front/src/Api/Events/CameraFollowPlayerEvent.ts new file mode 100644 index 00000000..cf34e7fc --- /dev/null +++ b/front/src/Api/Events/CameraFollowPlayerEvent.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; + +export const isCameraFollowPlayerEvent = new tg.IsInterface() + .withProperties({ + smooth: tg.isBoolean, + }) + .get(); +/** + * A message sent from the iFrame to the game to make the camera follow player. + */ +export type CameraFollowPlayerEvent = tg.GuardedType; diff --git a/front/src/Api/Events/CameraSetPositionEvent.ts b/front/src/Api/Events/CameraSetPositionEvent.ts new file mode 100644 index 00000000..ef421c44 --- /dev/null +++ b/front/src/Api/Events/CameraSetPositionEvent.ts @@ -0,0 +1,15 @@ +import * as tg from "generic-type-guard"; + +export const isCameraSetPositionEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + smooth: tg.isBoolean, + }) + .get(); +/** + * A message sent from the iFrame to the game to change the camera position. + */ +export type CameraSetPositionEvent = tg.GuardedType; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 2390145a..15a6ceef 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -31,6 +31,9 @@ import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPosition } from "./PlayerPosition"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; +import type { CameraSetPositionEvent } from "./CameraSetPositionEvent"; +import type { CameraFocusOnEvent } from "./CameraFocusOnEvent"; +import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -42,6 +45,9 @@ export interface TypedMessageEvent extends MessageEvent { export type IframeEventMap = { loadPage: LoadPageEvent; chat: ChatEvent; + cameraFocusOn: CameraFocusOnEvent; + cameraFollowPlayer: CameraFollowPlayerEvent; + cameraSetPosition: CameraSetPositionEvent; openPopup: OpenPopupEvent; closePopup: ClosePopupEvent; openTab: OpenTabEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 216a9510..30d41340 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,6 +33,9 @@ import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Store import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; +import { CameraSetPositionEvent, isCameraSetPositionEvent } from "./Events/CameraSetPositionEvent"; +import { CameraFocusOnEvent, isCameraFocusOnEvent } from "./Events/CameraFocusOnEvent"; +import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -56,6 +59,15 @@ class IframeListener { private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); + private readonly _cameraSetPositionStream: Subject = new Subject(); + public readonly cameraSetPositionStream = this._cameraSetPositionStream.asObservable(); + + private readonly _cameraFocusOnStream: Subject = new Subject(); + public readonly cameraFocusOnStream = this._cameraFocusOnStream.asObservable(); + + private readonly _cameraFollowPlayerStream: Subject = new Subject(); + public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable(); + private readonly _enablePlayerControlStream: Subject = new Subject(); public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable(); @@ -202,6 +214,12 @@ class IframeListener { this._hideLayerStream.next(payload.data); } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { this._setPropertyStream.next(payload.data); + } else if (payload.type === "cameraSetPosition" && isCameraSetPositionEvent(payload.data)) { + this._cameraSetPositionStream.next(payload.data); + } else if (payload.type === "cameraFocusOn" && isCameraFocusOnEvent(payload.data)) { + this._cameraFocusOnStream.next(payload.data); + } else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) { + this._cameraFollowPlayerStream.next(payload.data); } else if (payload.type === "chat" && isChatEvent(payload.data)) { scriptUtils.sendAnonymousChat(payload.data); } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index 4f62b94c..f562ce98 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -17,6 +17,27 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { + // this.cameraManager.enterFocusMode({ ...cameraSetPositionEvent }, undefined, cameraSetPositionEvent.smooth ? 1000 : 0); + console.log("camera set position"); + }) + ); + + this.iframeSubscriptionList.push( + iframeListener.cameraFocusOnStream.subscribe((cameraFocusOnEvent) => { + this.cameraManager.enterFocusMode( + { ...cameraFocusOnEvent }, + undefined, + cameraFocusOnEvent.smooth ? 1000 : 0 + ); + }) + ); + + this.iframeSubscriptionList.push( + iframeListener.cameraFollowPlayerStream.subscribe((cameraFollowPlayerEvent) => { + this.cameraManager.leaveFocusMode(this.CurrentPlayer, cameraFollowPlayerEvent.smooth ? 1000 : 0); + }) + ); + this.iframeSubscriptionList.push( iframeListener.playSoundStream.subscribe((playSoundEvent) => { const url = new URL(playSoundEvent.url, this.MapUrlFile); From 4ebc55a429f574eeed37f7c21fba3b8dae46e010 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 16 Dec 2021 14:31:26 +0100 Subject: [PATCH 21/29] subtle changes in CameraManager in order to distinguish between setPosition and focusOn --- front/src/Phaser/Game/CameraManager.ts | 90 ++++++++++++++++++++------ front/src/Phaser/Game/GameScene.ts | 7 +- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index 8f93959e..589b0a6a 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,13 +1,22 @@ import { Easing } from "../../types"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import type { Box } from "../../WebRtc/LayoutManager"; -import type { Player } from "../Player/Player"; +import { hasMovedEventName, Player } from "../Player/Player"; import type { WaScaleManager } from "../Services/WaScaleManager"; import type { GameScene } from "./GameScene"; export enum CameraMode { - Free = "Free", + /** + * Camera looks at certain point but is not locked and will start following the player on his movement + */ + Positioned = "Positioned", + /** + * Camera is actively following the player + */ Follow = "Follow", + /** + * Camera is focusing on certain point and will not break this focus even on player movement + */ Focus = "Focus", } @@ -17,12 +26,12 @@ export class CameraManager extends Phaser.Events.EventEmitter { private cameraBounds: { x: number; y: number }; private waScaleManager: WaScaleManager; - private cameraMode: CameraMode = CameraMode.Free; + private cameraMode: CameraMode = CameraMode.Positioned; private restoreZoomTween?: Phaser.Tweens.Tween; private startFollowTween?: Phaser.Tweens.Tween; - private cameraFollowTarget?: { x: number; y: number }; + private playerToFollow?: Player; constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) { super(); @@ -47,6 +56,51 @@ export class CameraManager extends Phaser.Events.EventEmitter { return this.camera; } + /** + * Set camera view to specific destination without changing current camera mode. Won't work if camera mode is set to Focus. + * @param setTo Viewport on which the camera should set the position + * @param duration Time for the transition im MS. If set to 0, transition will occur immediately + */ + public setPosition(setTo: { x: number; y: number; width: number; height: number }, duration: number = 1000): void { + if (this.cameraMode === CameraMode.Focus) { + return; + } + this.setCameraMode(CameraMode.Positioned); + const currentZoomModifier = this.waScaleManager.zoomModifier; + const zoomModifierChange = this.getZoomModifierChange(setTo.width, setTo.height); + this.camera.stopFollow(); + this.camera.pan( + setTo.x + setTo.width * 0.5, + setTo.y + setTo.height * 0.5, + duration, + Easing.SineEaseOut, + true, + (camera, progress, x, y) => { + if (this.cameraMode === CameraMode.Positioned) { + this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + } + if (progress === 1) { + this.playerToFollow?.once(hasMovedEventName, () => { + if (this.playerToFollow) { + this.startFollowPlayer(this.playerToFollow, duration); + } + }); + } + } + ); + } + + private getZoomModifierChange(width: number, height: number): number { + const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor(width, height); + const currentZoomModifier = this.waScaleManager.zoomModifier; + return targetZoomModifier - currentZoomModifier; + } + + /** + * Set camera to focus mode. As long as the camera is in the Focus mode, its view cannot be changed. + * @param setTo Viewport on which the camera should focus on + * @param duration Time for the transition im MS. If set to 0, transition will occur immediately + */ public enterFocusMode( focusOn: { x: number; y: number; width: number; height: number }, margin: number = 0, @@ -59,14 +113,10 @@ export class CameraManager extends Phaser.Events.EventEmitter { 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; + const zoomModifierChange = this.getZoomModifierChange(focusOn.width * marginMult, focusOn.height * marginMult); this.camera.stopFollow(); - this.cameraFollowTarget = undefined; + this.playerToFollow = undefined; this.camera.pan( focusOn.x + focusOn.width * 0.5 * marginMult, focusOn.y + focusOn.height * 0.5 * marginMult, @@ -81,15 +131,15 @@ export class CameraManager extends Phaser.Events.EventEmitter { public leaveFocusMode(player: Player, duration: number = 1000): void { this.waScaleManager.setFocusTarget(); - this.startFollow(player, duration); + this.startFollowPlayer(player, duration); this.restoreZoom(duration); } - public startFollow(target: object | Phaser.GameObjects.GameObject, duration: number = 0): void { - this.cameraFollowTarget = target as { x: number; y: number }; + public startFollowPlayer(player: Player, duration: number = 0): void { + this.playerToFollow = player; this.setCameraMode(CameraMode.Follow); if (duration === 0) { - this.camera.startFollow(target, true); + this.camera.startFollow(player, true); return; } const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY }; @@ -99,17 +149,17 @@ export class CameraManager extends Phaser.Events.EventEmitter { duration, ease: Easing.SineEaseOut, onUpdate: (tween: Phaser.Tweens.Tween) => { - if (!this.cameraFollowTarget) { + if (!this.playerToFollow) { return; } const shiftX = - (this.cameraFollowTarget.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue(); + (this.playerToFollow.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.playerToFollow.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); + this.camera.startFollow(player, true); }, }); } @@ -131,8 +181,8 @@ export class CameraManager extends Phaser.Events.EventEmitter { ); } - public isCameraLocked(): boolean { - return this.cameraMode === CameraMode.Focus; + public isCameraZoomLocked(): boolean { + return [CameraMode.Focus, CameraMode.Positioned].includes(this.cameraMode); } private setCameraMode(mode: CameraMode): void { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 00d205a9..e71a35a3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -561,7 +561,7 @@ export class GameScene extends DirtyScene { waScaleManager ); biggestAvailableAreaStore.recompute(); - this.cameraManager.startFollow(this.CurrentPlayer); + this.cameraManager.startFollowPlayer(this.CurrentPlayer); this.animatedTiles.init(this.Map); this.events.on("tileanimationupdate", () => (this.dirty = true)); @@ -1072,8 +1072,7 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.cameraSetPositionStream.subscribe((cameraSetPositionEvent) => { - // this.cameraManager.enterFocusMode({ ...cameraSetPositionEvent }, undefined, cameraSetPositionEvent.smooth ? 1000 : 0); - console.log("camera set position"); + this.cameraManager.setPosition({ ...cameraSetPositionEvent }, cameraSetPositionEvent.smooth ? 1000 : 0); }) ); @@ -2045,7 +2044,7 @@ ${escapedMessage} } zoomByFactor(zoomFactor: number) { - if (this.cameraManager.isCameraLocked()) { + if (this.cameraManager.isCameraZoomLocked()) { return; } waScaleManager.zoomModifier *= zoomFactor; From 4871b406dedc8d53ef3e9722289fbf9859157699 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 16 Dec 2021 16:08:41 +0100 Subject: [PATCH 22/29] test map for Camera API --- maps/tests/CameraApi/camera_api_test.json | 158 ++++++++++++++++++++++ maps/tests/CameraApi/script.php | 60 ++++++++ maps/tests/index.html | 8 ++ 3 files changed, 226 insertions(+) create mode 100644 maps/tests/CameraApi/camera_api_test.json create mode 100644 maps/tests/CameraApi/script.php diff --git a/maps/tests/CameraApi/camera_api_test.json b/maps/tests/CameraApi/camera_api_test.json new file mode 100644 index 00000000..ea63ee57 --- /dev/null +++ b/maps/tests/CameraApi/camera_api_test.json @@ -0,0 +1,158 @@ +{ "compressionlevel":-1, + "height":30, + "infinite":false, + "layers":[ + { + "data":[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, 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, 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, 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":30, + "id":1, + "name":"floor", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":30, + "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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":115.775966464835, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Set Position - Move the camera to the given point but do not lock it. If the player moves, camera will back to following him", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.4375, + "x":68.4021076998051, + "y":8.73391812865529 + }, + { + "height":115.776, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Focus On - Works like for Focusable Zones. Camera will be locked and won't move unless we explicitly change the mode", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.438, + "x":64.7309301350722, + "y":126.555409408477 + }, + { + "height":115.776, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Follow Player - Camera will start following the player if doesn't do this already", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.438, + "x":64.7309301350722, + "y":256.224715416861 + }, + { + "height":115.776, + "id":5, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Both Set Position and Focus On will lock the ability of changing the game zoom", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.438, + "x":65.848768979972, + "y":351.241017233349 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":6, + "nextobjectid":6, + "orientation":"orthogonal", + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "renderorder":"right-down", + "tiledversion":"1.7.2", + "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.6", + "width":30 +} \ No newline at end of file diff --git a/maps/tests/CameraApi/script.php b/maps/tests/CameraApi/script.php new file mode 100644 index 00000000..2db030f3 --- /dev/null +++ b/maps/tests/CameraApi/script.php @@ -0,0 +1,60 @@ + + + + + + + +X:
+Y:
+width:
+height:
+Smooth:
+ + + + + + + diff --git a/maps/tests/index.html b/maps/tests/index.html index c920c876..053e9e55 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -203,6 +203,14 @@ Test set tiles + + + Success Failure Pending + + + Test camera API + + Success Failure Pending From f8353bd7b5f55841183764ead3a95e72f45e62e3 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Fri, 17 Dec 2021 11:05:11 +0100 Subject: [PATCH 23/29] send camera update event from CameraManager --- front/src/Phaser/Game/CameraManager.ts | 35 +++++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 47 ++++++++++++++------------ maps/tests/CameraApi/script.php | 3 +- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index 589b0a6a..63e4f1be 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -20,6 +20,18 @@ export enum CameraMode { Focus = "Focus", } +export enum CameraManagerEvent { + CameraUpdate = "CameraUpdate", +} + +export interface CameraManagerEventCameraUpdateData { + x: number; + y: number; + width: number; + height: number; + zoom: number; +} + export class CameraManager extends Phaser.Events.EventEmitter { private scene: GameScene; private camera: Phaser.Cameras.Scene2D.Camera; @@ -78,6 +90,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { (camera, progress, x, y) => { if (this.cameraMode === CameraMode.Positioned) { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); } if (progress === 1) { this.playerToFollow?.once(hasMovedEventName, () => { @@ -125,6 +138,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { true, (camera, progress, x, y) => { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); } ); } @@ -157,6 +171,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { const shiftY = (this.playerToFollow.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue(); this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); }, onComplete: () => { this.camera.startFollow(player, true); @@ -205,6 +220,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { ease: Easing.SineEaseOut, onUpdate: (tween: Phaser.Tweens.Tween) => { this.waScaleManager.zoomModifier = tween.getValue(); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); }, }); } @@ -222,7 +238,26 @@ export class CameraManager extends Phaser.Events.EventEmitter { return; } this.camera.centerOn(focusOn.x + focusOn.width * 0.5, focusOn.y + focusOn.height * 0.5); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); } ); + + this.camera.on("followupdate", () => { + this.sendCameraUpdateEvent(); + }); + } + + private sendCameraUpdateEvent(): void { + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + } + + private getCameraUpdateEventData(): CameraManagerEventCameraUpdateData { + return { + x: this.camera.worldView.x, + y: this.camera.worldView.y, + width: this.camera.worldView.width, + height: this.camera.worldView.height, + zoom: this.camera.scaleManager.zoom, + }; } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index e71a35a3..d4f368d4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -65,7 +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 { CameraManager, CameraManagerEvent, CameraManagerEventCameraUpdateData } from "./CameraManager"; import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent"; import type { Character } from "../Entity/Character"; @@ -1102,28 +1102,31 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.trackCameraUpdateStream.subscribe(() => { if (!this.firstCameraUpdateSent) { - this.cameras.main.on("followupdate", (camera: Camera) => { - const cameraEvent: WasCameraUpdatedEvent = { - x: camera.worldView.x, - y: camera.worldView.y, - width: camera.worldView.width, - height: camera.worldView.height, - zoom: camera.scaleManager.zoom, - }; - if ( - this.lastCameraEvent?.x == cameraEvent.x && - this.lastCameraEvent?.y == cameraEvent.y && - this.lastCameraEvent?.width == cameraEvent.width && - this.lastCameraEvent?.height == cameraEvent.height && - this.lastCameraEvent?.zoom == cameraEvent.zoom - ) { - return; - } + this.cameraManager.on( + CameraManagerEvent.CameraUpdate, + (data: CameraManagerEventCameraUpdateData) => { + const cameraEvent: WasCameraUpdatedEvent = { + x: data.x, + y: data.y, + width: data.width, + height: data.height, + zoom: data.zoom, + }; + if ( + this.lastCameraEvent?.x == cameraEvent.x && + this.lastCameraEvent?.y == cameraEvent.y && + this.lastCameraEvent?.width == cameraEvent.width && + this.lastCameraEvent?.height == cameraEvent.height && + this.lastCameraEvent?.zoom == cameraEvent.zoom + ) { + return; + } - this.lastCameraEvent = cameraEvent; - iframeListener.sendCameraUpdated(cameraEvent); - this.firstCameraUpdateSent = true; - }); + this.lastCameraEvent = cameraEvent; + iframeListener.sendCameraUpdated(cameraEvent); + this.firstCameraUpdateSent = true; + } + ); iframeListener.sendCameraUpdated(this.cameras.main); } diff --git a/maps/tests/CameraApi/script.php b/maps/tests/CameraApi/script.php index 2db030f3..d8fb03a7 100644 --- a/maps/tests/CameraApi/script.php +++ b/maps/tests/CameraApi/script.php @@ -4,7 +4,8 @@ -X:
-Y:
-width:
-height:
+X:
+Y:
+width:
+height:
Smooth:
Lock:
From 17e9c3c58623b8e3d8a7a3290a0e4849bd922c9e Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Fri, 14 Jan 2022 11:31:49 +0100 Subject: [PATCH 28/29] handle instant camera view change --- front/src/Phaser/Game/CameraManager.ts | 54 ++++++++++++++++++-------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index eefdeeb3..1cbc6519 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -81,10 +81,22 @@ export class CameraManager extends Phaser.Events.EventEmitter { } this.setCameraMode(CameraMode.Positioned); this.waScaleManager.saveZoom(); - const currentZoomModifier = this.waScaleManager.zoomModifier; - const zoomModifierChange = - setTo.width && setTo.height ? this.getZoomModifierChange(setTo.width, setTo.height) : 0; this.camera.stopFollow(); + + const currentZoomModifier = this.waScaleManager.zoomModifier; + const zoomModifierChange = this.getZoomModifierChange(setTo.width, setTo.height); + + if (duration === 0) { + this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange; + this.camera.centerOn(setTo.x, setTo.y); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + this.playerToFollow?.once(hasMovedEventName, () => { + if (this.playerToFollow) { + this.startFollowPlayer(this.playerToFollow, duration); + } + }); + return; + } this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { if (this.cameraMode === CameraMode.Positioned) { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; @@ -100,12 +112,6 @@ export class CameraManager extends Phaser.Events.EventEmitter { }); } - private getZoomModifierChange(width: number, height: number): number { - const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor(width, height); - const currentZoomModifier = this.waScaleManager.zoomModifier; - return targetZoomModifier - currentZoomModifier; - } - /** * Set camera to focus mode. As long as the camera is in the Focus mode, its view cannot be changed. * @param setTo Viewport on which the camera should focus on @@ -115,19 +121,23 @@ export class CameraManager extends Phaser.Events.EventEmitter { this.setCameraMode(CameraMode.Focus); this.waScaleManager.saveZoom(); this.waScaleManager.setFocusTarget(focusOn); - this.cameraLocked = true; + this.unlockCameraWithDelay(duration); this.restoreZoomTween?.stop(); this.startFollowTween?.stop(); - const marginMult = 1 + margin; - const currentZoomModifier = this.waScaleManager.zoomModifier; - const zoomModifierChange = - focusOn.width && focusOn.height - ? this.getZoomModifierChange(focusOn.width * marginMult, focusOn.height * marginMult) - : 0; this.camera.stopFollow(); this.playerToFollow = undefined; + + const currentZoomModifier = this.waScaleManager.zoomModifier; + const zoomModifierChange = this.getZoomModifierChange(focusOn.width, focusOn.height, 1 + margin); + + if (duration === 0) { + this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange; + this.camera.centerOn(focusOn.x, focusOn.y); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + return; + } this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); @@ -192,6 +202,18 @@ export class CameraManager extends Phaser.Events.EventEmitter { return this.cameraLocked; } + private getZoomModifierChange(width?: number, height?: number, multiplier: number = 1): number { + if (!width || !height) { + return 0; + } + const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor( + width * multiplier, + height * multiplier + ); + const currentZoomModifier = this.waScaleManager.zoomModifier; + return targetZoomModifier - currentZoomModifier; + } + private unlockCameraWithDelay(delay: number): void { this.scene.time.delayedCall(delay, () => { this.cameraLocked = false; From 7ee41bad30dacf894d1f9a940e1dec7e3fd0d35e Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Fri, 14 Jan 2022 11:41:37 +0100 Subject: [PATCH 29/29] change setViewport action to set for CameraAPI --- .../{CameraSetViewportEvent.ts => CameraSetEvent.ts} | 4 ++-- front/src/Api/Events/IframeEvent.ts | 4 ++-- front/src/Api/IframeListener.ts | 10 +++++----- front/src/Api/iframe/camera.ts | 4 ++-- front/src/Phaser/Game/CameraManager.ts | 4 ++++ front/src/Phaser/Game/GameScene.ts | 10 +++++----- maps/tests/CameraApi/script.php | 8 ++++---- 7 files changed, 24 insertions(+), 20 deletions(-) rename front/src/Api/Events/{CameraSetViewportEvent.ts => CameraSetEvent.ts} (71%) diff --git a/front/src/Api/Events/CameraSetViewportEvent.ts b/front/src/Api/Events/CameraSetEvent.ts similarity index 71% rename from front/src/Api/Events/CameraSetViewportEvent.ts rename to front/src/Api/Events/CameraSetEvent.ts index b1d1df0e..a3da7c62 100644 --- a/front/src/Api/Events/CameraSetViewportEvent.ts +++ b/front/src/Api/Events/CameraSetEvent.ts @@ -1,6 +1,6 @@ import * as tg from "generic-type-guard"; -export const isCameraSetViewportEvent = new tg.IsInterface() +export const isCameraSetEvent = new tg.IsInterface() .withProperties({ x: tg.isNumber, y: tg.isNumber, @@ -13,4 +13,4 @@ export const isCameraSetViewportEvent = new tg.IsInterface() /** * A message sent from the iFrame to the game to change the camera position. */ -export type CameraSetViewportEvent = tg.GuardedType; +export type CameraSetEvent = tg.GuardedType; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 0df53fbe..93d0735c 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -31,7 +31,7 @@ import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPosition } from "./PlayerPosition"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; -import type { CameraSetViewportEvent } from "./CameraSetViewportEvent"; +import type { CameraSetEvent } from "./CameraSetEvent"; import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; import { isColorEvent } from "./ColorEvent"; @@ -46,7 +46,7 @@ export type IframeEventMap = { loadPage: LoadPageEvent; chat: ChatEvent; cameraFollowPlayer: CameraFollowPlayerEvent; - cameraSetViewport: CameraSetViewportEvent; + cameraSet: CameraSetEvent; openPopup: OpenPopupEvent; closePopup: ClosePopupEvent; openTab: OpenTabEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 49d8b37b..0e6cebbe 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,7 +33,7 @@ import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Store import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; -import { CameraSetViewportEvent, isCameraSetViewportEvent } from "./Events/CameraSetViewportEvent"; +import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; type AnswererCallback = ( @@ -58,8 +58,8 @@ class IframeListener { private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); - private readonly _cameraSetViewportStream: Subject = new Subject(); - public readonly cameraSetViewportStream = this._cameraSetViewportStream.asObservable(); + private readonly _cameraSetStream: Subject = new Subject(); + public readonly cameraSetStream = this._cameraSetStream.asObservable(); private readonly _cameraFollowPlayerStream: Subject = new Subject(); public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable(); @@ -210,8 +210,8 @@ class IframeListener { this._hideLayerStream.next(payload.data); } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { this._setPropertyStream.next(payload.data); - } else if (payload.type === "cameraSetViewport" && isCameraSetViewportEvent(payload.data)) { - this._cameraSetViewportStream.next(payload.data); + } else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) { + this._cameraSetStream.next(payload.data); } else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) { this._cameraFollowPlayerStream.next(payload.data); } else if (payload.type === "chat" && isChatEvent(payload.data)) { diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index c509d87c..38199e0d 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -17,7 +17,7 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + if (progress === 1) { + // NOTE: Making sure the last action will be centering after zoom change + this.camera.centerOn(focusOn.x, focusOn.y); + } this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f4b07e6d..b031cadd 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1125,11 +1125,11 @@ ${escapedMessage} ); this.iframeSubscriptionList.push( - iframeListener.cameraSetViewportStream.subscribe((cameraSetViewportEvent) => { - const duration = cameraSetViewportEvent.smooth ? 1000 : 0; - cameraSetViewportEvent.lock - ? this.cameraManager.enterFocusMode({ ...cameraSetViewportEvent }, undefined, duration) - : this.cameraManager.setPosition({ ...cameraSetViewportEvent }, duration); + iframeListener.cameraSetStream.subscribe((cameraSetEvent) => { + const duration = cameraSetEvent.smooth ? 1000 : 0; + cameraSetEvent.lock + ? this.cameraManager.enterFocusMode({ ...cameraSetEvent }, undefined, duration) + : this.cameraManager.setPosition({ ...cameraSetEvent }, duration); }) ); diff --git a/maps/tests/CameraApi/script.php b/maps/tests/CameraApi/script.php index 1fa9b5e1..24f7fff4 100644 --- a/maps/tests/CameraApi/script.php +++ b/maps/tests/CameraApi/script.php @@ -8,7 +8,7 @@ WA.camera.onCameraUpdate((worldView) => console.log(worldView)); WA.onInit().then(() => { console.log('After WA init'); - const setViewportButton = document.getElementById('setViewportButton'); + const setCameraButton = document.getElementById('setCameraButton'); const followPlayerButton = document.getElementById('followPlayerButton'); const xField = document.getElementById('x'); const yField = document.getElementById('y'); @@ -17,8 +17,8 @@ const smoothField = document.getElementById('smooth'); const lockField = document.getElementById('lock'); - setViewportButton.addEventListener('click', () => { - WA.camera.setViewport( + setCameraButton.addEventListener('click', () => { + WA.camera.set( parseInt(xField.value), parseInt(yField.value), widthField.value ? parseInt(widthField.value) : undefined, @@ -43,7 +43,7 @@ height:
Smooth:
Lock:
- +