2021-12-03 10:11:16 +01:00
|
|
|
import { Easing } from "../../types";
|
|
|
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
|
|
|
import type { Box } from "../../WebRtc/LayoutManager";
|
2021-12-16 14:31:26 +01:00
|
|
|
import { hasMovedEventName, Player } from "../Player/Player";
|
2022-01-13 16:43:58 +01:00
|
|
|
import { WaScaleManager, WaScaleManagerEvent, WaScaleManagerFocusTarget } from "../Services/WaScaleManager";
|
2021-12-03 10:11:16 +01:00
|
|
|
import type { GameScene } from "./GameScene";
|
2021-12-02 13:20:40 +01:00
|
|
|
|
2021-12-02 14:44:13 +01:00
|
|
|
export enum CameraMode {
|
2021-12-16 14:31:26 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-12-03 10:11:16 +01:00
|
|
|
Follow = "Follow",
|
2021-12-16 14:31:26 +01:00
|
|
|
/**
|
|
|
|
* Camera is focusing on certain point and will not break this focus even on player movement
|
|
|
|
*/
|
2021-12-03 10:11:16 +01:00
|
|
|
Focus = "Focus",
|
2021-12-02 14:44:13 +01:00
|
|
|
}
|
|
|
|
|
2021-12-17 11:05:11 +01:00
|
|
|
export enum CameraManagerEvent {
|
|
|
|
CameraUpdate = "CameraUpdate",
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface CameraManagerEventCameraUpdateData {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
zoom: number;
|
|
|
|
}
|
|
|
|
|
2021-12-02 13:20:40 +01:00
|
|
|
export class CameraManager extends Phaser.Events.EventEmitter {
|
|
|
|
private scene: GameScene;
|
|
|
|
private camera: Phaser.Cameras.Scene2D.Camera;
|
2021-12-03 10:11:16 +01:00
|
|
|
private cameraBounds: { x: number; y: number };
|
2021-12-02 13:20:40 +01:00
|
|
|
private waScaleManager: WaScaleManager;
|
|
|
|
|
2021-12-16 14:31:26 +01:00
|
|
|
private cameraMode: CameraMode = CameraMode.Positioned;
|
2021-12-02 14:44:13 +01:00
|
|
|
|
|
|
|
private restoreZoomTween?: Phaser.Tweens.Tween;
|
2021-12-08 11:31:49 +01:00
|
|
|
private startFollowTween?: Phaser.Tweens.Tween;
|
|
|
|
|
2021-12-16 14:31:26 +01:00
|
|
|
private playerToFollow?: Player;
|
2022-01-12 12:08:11 +01:00
|
|
|
private cameraLocked: boolean;
|
2021-12-02 13:20:40 +01:00
|
|
|
|
2021-12-03 10:11:16 +01:00
|
|
|
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
2021-12-02 13:20:40 +01:00
|
|
|
super();
|
|
|
|
this.scene = scene;
|
|
|
|
|
|
|
|
this.camera = scene.cameras.main;
|
|
|
|
this.cameraBounds = cameraBounds;
|
2022-01-12 12:08:11 +01:00
|
|
|
this.cameraLocked = false;
|
2021-12-02 13:20:40 +01:00
|
|
|
|
|
|
|
this.waScaleManager = waScaleManager;
|
|
|
|
|
|
|
|
this.initCamera();
|
2021-12-07 12:48:08 +01:00
|
|
|
|
2021-12-07 13:18:36 +01:00
|
|
|
this.bindEventHandlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
public destroy(): void {
|
2022-01-13 16:43:58 +01:00
|
|
|
this.scene.game.events.off(WaScaleManagerEvent.RefreshFocusOnTarget);
|
2021-12-07 13:18:36 +01:00
|
|
|
super.destroy();
|
2021-12-02 13:20:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public getCamera(): Phaser.Cameras.Scene2D.Camera {
|
|
|
|
return this.camera;
|
|
|
|
}
|
|
|
|
|
2021-12-16 14:31:26 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2022-01-13 16:43:58 +01:00
|
|
|
public setPosition(setTo: WaScaleManagerFocusTarget, duration: number = 1000): void {
|
2021-12-16 14:31:26 +01:00
|
|
|
if (this.cameraMode === CameraMode.Focus) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setCameraMode(CameraMode.Positioned);
|
2022-01-13 16:17:49 +01:00
|
|
|
this.waScaleManager.saveZoom();
|
2021-12-16 14:31:26 +01:00
|
|
|
this.camera.stopFollow();
|
2022-01-14 11:31:49 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2022-01-13 18:54:38 +01:00
|
|
|
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;
|
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
2021-12-16 14:31:26 +01:00
|
|
|
}
|
2022-01-13 18:54:38 +01:00
|
|
|
if (progress === 1) {
|
|
|
|
this.playerToFollow?.once(hasMovedEventName, () => {
|
|
|
|
if (this.playerToFollow) {
|
|
|
|
this.startFollowPlayer(this.playerToFollow, duration);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2021-12-16 14:31:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2022-01-13 16:43:58 +01:00
|
|
|
public enterFocusMode(focusOn: WaScaleManagerFocusTarget, margin: number = 0, duration: number = 1000): void {
|
2021-12-02 14:44:13 +01:00
|
|
|
this.setCameraMode(CameraMode.Focus);
|
2021-12-02 13:41:52 +01:00
|
|
|
this.waScaleManager.saveZoom();
|
2021-12-07 12:48:08 +01:00
|
|
|
this.waScaleManager.setFocusTarget(focusOn);
|
2022-01-12 12:08:11 +01:00
|
|
|
this.cameraLocked = true;
|
2022-01-14 11:31:49 +01:00
|
|
|
|
2022-01-12 12:08:11 +01:00
|
|
|
this.unlockCameraWithDelay(duration);
|
2021-12-02 14:44:13 +01:00
|
|
|
this.restoreZoomTween?.stop();
|
2021-12-08 11:31:49 +01:00
|
|
|
this.startFollowTween?.stop();
|
2021-12-02 13:20:40 +01:00
|
|
|
this.camera.stopFollow();
|
2021-12-16 14:31:26 +01:00
|
|
|
this.playerToFollow = undefined;
|
2022-01-14 11:31:49 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2022-01-13 18:54:38 +01:00
|
|
|
this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
|
|
|
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
2022-01-14 11:41:37 +01:00
|
|
|
if (progress === 1) {
|
|
|
|
// NOTE: Making sure the last action will be centering after zoom change
|
|
|
|
this.camera.centerOn(focusOn.x, focusOn.y);
|
|
|
|
}
|
2022-01-13 18:54:38 +01:00
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
|
|
|
});
|
2021-12-02 13:20:40 +01:00
|
|
|
}
|
|
|
|
|
2022-01-12 12:08:11 +01:00
|
|
|
public leaveFocusMode(player: Player, duration = 0): void {
|
2021-12-07 12:48:08 +01:00
|
|
|
this.waScaleManager.setFocusTarget();
|
2022-01-12 12:08:11 +01:00
|
|
|
this.unlockCameraWithDelay(duration);
|
2021-12-16 14:31:26 +01:00
|
|
|
this.startFollowPlayer(player, duration);
|
2021-12-16 13:41:28 +01:00
|
|
|
this.restoreZoom(duration);
|
2021-12-02 14:44:13 +01:00
|
|
|
}
|
|
|
|
|
2021-12-16 14:31:26 +01:00
|
|
|
public startFollowPlayer(player: Player, duration: number = 0): void {
|
|
|
|
this.playerToFollow = player;
|
2021-12-02 14:44:13 +01:00
|
|
|
this.setCameraMode(CameraMode.Follow);
|
2021-12-07 17:03:51 +01:00
|
|
|
if (duration === 0) {
|
2021-12-16 14:31:26 +01:00
|
|
|
this.camera.startFollow(player, true);
|
2021-12-07 17:03:51 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-12-08 11:31:49 +01:00
|
|
|
const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY };
|
|
|
|
this.startFollowTween = this.scene.tweens.addCounter({
|
|
|
|
from: 0,
|
|
|
|
to: 1,
|
2021-12-07 17:03:51 +01:00
|
|
|
duration,
|
2021-12-08 11:31:49 +01:00
|
|
|
ease: Easing.SineEaseOut,
|
|
|
|
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
2021-12-16 14:31:26 +01:00
|
|
|
if (!this.playerToFollow) {
|
2021-12-08 11:31:49 +01:00
|
|
|
return;
|
2021-12-07 17:03:51 +01:00
|
|
|
}
|
2021-12-08 11:31:49 +01:00
|
|
|
const shiftX =
|
2021-12-16 14:31:26 +01:00
|
|
|
(this.playerToFollow.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue();
|
2021-12-08 11:31:49 +01:00
|
|
|
const shiftY =
|
2021-12-16 14:31:26 +01:00
|
|
|
(this.playerToFollow.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue();
|
2021-12-08 11:31:49 +01:00
|
|
|
this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY);
|
2021-12-17 11:05:11 +01:00
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
2021-12-08 11:31:49 +01:00
|
|
|
},
|
|
|
|
onComplete: () => {
|
2021-12-16 14:31:26 +01:00
|
|
|
this.camera.startFollow(player, true);
|
2021-12-08 11:31:49 +01:00
|
|
|
},
|
|
|
|
});
|
2021-12-02 13:20:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the offset of the character compared to the center of the screen according to the layout manager
|
|
|
|
* (tries to put the character in the center of the remaining space if there is a discussion going on.
|
|
|
|
*/
|
|
|
|
public updateCameraOffset(array: Box): void {
|
|
|
|
const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
|
|
|
|
const yCenter = (array.yEnd - array.yStart) / 2 + array.yStart;
|
|
|
|
|
|
|
|
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>("#game canvas");
|
|
|
|
// Let's put this in Game coordinates by applying the zoom level:
|
|
|
|
|
|
|
|
this.camera.setFollowOffset(
|
|
|
|
((xCenter - game.offsetWidth / 2) * window.devicePixelRatio) / this.scene.scale.zoom,
|
|
|
|
((yCenter - game.offsetHeight / 2) * window.devicePixelRatio) / this.scene.scale.zoom
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-02 14:44:13 +01:00
|
|
|
public isCameraLocked(): boolean {
|
2022-01-12 12:08:11 +01:00
|
|
|
return this.cameraLocked;
|
|
|
|
}
|
|
|
|
|
2022-01-14 11:31:49 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-12 12:08:11 +01:00
|
|
|
private unlockCameraWithDelay(delay: number): void {
|
|
|
|
this.scene.time.delayedCall(delay, () => {
|
|
|
|
this.cameraLocked = false;
|
|
|
|
});
|
2021-12-02 14:44:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private setCameraMode(mode: CameraMode): void {
|
|
|
|
if (this.cameraMode === mode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.cameraMode = mode;
|
|
|
|
}
|
|
|
|
|
2021-12-07 17:03:51 +01:00
|
|
|
private restoreZoom(duration: number = 0): void {
|
|
|
|
if (duration === 0) {
|
|
|
|
this.waScaleManager.zoomModifier = this.waScaleManager.getSaveZoom();
|
|
|
|
return;
|
|
|
|
}
|
2021-12-02 14:44:13 +01:00
|
|
|
this.restoreZoomTween?.stop();
|
|
|
|
this.restoreZoomTween = this.scene.tweens.addCounter({
|
|
|
|
from: this.waScaleManager.zoomModifier,
|
|
|
|
to: this.waScaleManager.getSaveZoom(),
|
2021-12-07 17:03:51 +01:00
|
|
|
duration,
|
2021-12-02 14:44:13 +01:00
|
|
|
ease: Easing.SineEaseOut,
|
|
|
|
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
|
|
|
this.waScaleManager.zoomModifier = tween.getValue();
|
2021-12-17 11:05:11 +01:00
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
2021-12-03 10:11:16 +01:00
|
|
|
},
|
2021-12-02 14:44:13 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-02 13:20:40 +01:00
|
|
|
private initCamera() {
|
|
|
|
this.camera = this.scene.cameras.main;
|
|
|
|
this.camera.setBounds(0, 0, this.cameraBounds.x, this.cameraBounds.y);
|
|
|
|
}
|
2021-12-07 13:18:36 +01:00
|
|
|
|
|
|
|
private bindEventHandlers(): void {
|
|
|
|
this.scene.game.events.on(
|
2022-01-13 16:43:58 +01:00
|
|
|
WaScaleManagerEvent.RefreshFocusOnTarget,
|
2021-12-07 13:18:36 +01:00
|
|
|
(focusOn: { x: number; y: number; width: number; height: number }) => {
|
|
|
|
if (!focusOn) {
|
|
|
|
return;
|
|
|
|
}
|
2022-01-13 18:54:38 +01:00
|
|
|
this.camera.centerOn(focusOn.x, focusOn.y);
|
2021-12-17 11:05:11 +01:00
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
2021-12-07 13:18:36 +01:00
|
|
|
}
|
|
|
|
);
|
2021-12-17 11:05:11 +01:00
|
|
|
|
|
|
|
this.camera.on("followupdate", () => {
|
2022-01-13 16:08:16 +01:00
|
|
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
2021-12-17 11:05:11 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
2021-12-07 13:18:36 +01:00
|
|
|
}
|
2021-12-03 10:11:16 +01:00
|
|
|
}
|