From ee88ec504a9db5189361d56edccb1b268816b26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9lemy?= <31370477+BarthPaleologue@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:57:21 +0200 Subject: [PATCH] added pause menu + continued to replacce register by Observable --- src/html/pauseMenu.html | 17 ++++++++ src/styles/index.scss | 2 + src/styles/pauseMenu/index.scss | 63 ++++++++++++++++++++++++++++ src/ts/blackHoleDemo.ts | 1 + src/ts/controller/inputs/mouse.ts | 16 +++---- src/ts/controller/spaceEngine.ts | 40 +++++++++++++++++- src/ts/index.ts | 6 +-- src/ts/randomizer.ts | 3 ++ src/ts/spaceship/abstractThruster.ts | 2 +- src/ts/starmap/starMap.ts | 12 +++++- src/ts/ui/pauseMenu.ts | 41 ++++++++++++++++++ 11 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 src/html/pauseMenu.html create mode 100644 src/styles/pauseMenu/index.scss create mode 100644 src/ts/ui/pauseMenu.ts diff --git a/src/html/pauseMenu.html b/src/html/pauseMenu.html new file mode 100644 index 000000000..0140583fd --- /dev/null +++ b/src/html/pauseMenu.html @@ -0,0 +1,17 @@ +
+ +
+ +
+

Paused

+ +
+ +
Take Screenshot
+ +
Share System
+ +
Resume
+ +
+
\ No newline at end of file diff --git a/src/styles/index.scss b/src/styles/index.scss index ffc73348e..e837e0312 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -16,6 +16,8 @@ body { @import "helmetOverlay/helmetOverlay"; + @import "pauseMenu/index.scss"; + #renderer { z-index: 1; float: left; diff --git a/src/styles/pauseMenu/index.scss b/src/styles/pauseMenu/index.scss new file mode 100644 index 000000000..5a8ae5a95 --- /dev/null +++ b/src/styles/pauseMenu/index.scss @@ -0,0 +1,63 @@ +#pauseMask { + z-index: 9; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); +} + +#pauseMenu { + position: absolute; + width: 300px; + height: 300px; + top: calc(50% - 150px); + left: calc(50% - 150px); + background: rgba(0, 0, 0, 0.9); + + z-index: 10; + + display: grid; + grid-template-rows: 100px auto; + + padding-bottom: 20px; + + box-shadow: 0 0 50px rgba(255, 255, 255, 0.8); + + font-family: sans-serif; + + h1 { + color: white; + text-align: center; + } + + .buttons { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + row-gap: 10px; + + .button { + text-align: center; + padding: 10px 20px; + background: white; + color: black; + width: 120px; + cursor: pointer; + transition: .2s; + + &:hover { + background: darken(white, 20%); + color: black; + } + + &:active { + background: darken(white, 40%); + color: black; + } + } + } +} \ No newline at end of file diff --git a/src/ts/blackHoleDemo.ts b/src/ts/blackHoleDemo.ts index 50310777b..016a5c619 100644 --- a/src/ts/blackHoleDemo.ts +++ b/src/ts/blackHoleDemo.ts @@ -39,6 +39,7 @@ spaceshipController.addInput(gamepad); scene.setActiveController(spaceshipController); engine.registerStarSystemUpdateCallback(() => { + if (engine.isPaused()) return; if (scene.getActiveController() != spaceshipController) return; const shipPosition = spaceshipController.getTransform().getAbsolutePosition(); diff --git a/src/ts/controller/inputs/mouse.ts b/src/ts/controller/inputs/mouse.ts index ff5738abc..e76203ebd 100644 --- a/src/ts/controller/inputs/mouse.ts +++ b/src/ts/controller/inputs/mouse.ts @@ -1,3 +1,4 @@ +import { Observable } from "@babylonjs/core/Misc/observable"; import { clamp } from "../../utils/math"; import { Input, InputType } from "./input"; @@ -16,8 +17,8 @@ export class Mouse implements Input { deadAreaRadius = 100; private canvas: HTMLCanvasElement; - private onMouseEnterListeners: (() => void)[] = []; - private onMouseLeaveListeners: (() => void)[] = []; + readonly onMouseEnterObservable: Observable = new Observable(); + readonly onMouseLeaveObservable: Observable = new Observable(); constructor(canvas: HTMLCanvasElement, deadAreaRadius = 50) { this.deadAreaRadius = deadAreaRadius; @@ -35,20 +36,13 @@ export class Mouse implements Input { }); document.addEventListener("mouseenter", () => { - this.onMouseEnterListeners.forEach((listener) => listener()); + this.onMouseEnterObservable.notifyObservers(); }); document.addEventListener("mouseleave", () => { - this.onMouseLeaveListeners.forEach((listener) => listener()); + this.onMouseLeaveObservable.notifyObservers(); }); } - addOnMouseEnterListener(listener: () => void) { - this.onMouseEnterListeners.push(listener); - } - addOnMouseLeaveListener(listener: () => void) { - this.onMouseLeaveListeners.push(listener); - } - getRoll() { const d2 = this.dxToCenter ** 2 + this.dyToCenter ** 2; const adaptedLength = Math.max(Math.log(d2 / this.deadAreaRadius ** 2), 0) / 3; diff --git a/src/ts/controller/spaceEngine.ts b/src/ts/controller/spaceEngine.ts index 646845997..fa5e22571 100644 --- a/src/ts/controller/spaceEngine.ts +++ b/src/ts/controller/spaceEngine.ts @@ -30,6 +30,7 @@ import { Animation } from "@babylonjs/core/Animations/animation"; import { Observable } from "@babylonjs/core/Misc/observable"; import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight"; import { OrbitRenderer } from "../view/orbitRenderer"; +import { PauseMenu } from "../ui/pauseMenu"; enum EngineState { RUNNING, @@ -40,6 +41,7 @@ export class SpaceEngine { // UI private readonly helmetOverlay: HelmetOverlay; readonly bodyEditor: BodyEditor; + private readonly pauseMenu: PauseMenu; readonly canvas: HTMLCanvasElement; private isFullscreen = false; private videoRecorder: VideoRecorder | null = null; @@ -68,6 +70,15 @@ export class SpaceEngine { constructor() { this.helmetOverlay = new HelmetOverlay(); this.bodyEditor = new BodyEditor(); + this.pauseMenu = new PauseMenu(); + + this.pauseMenu.onResume.add(() => this.resume()); + this.pauseMenu.onScreenshot.add(() => this.takeScreenshot()); + this.pauseMenu.onShare.add(() => { + const seed = this.getStarSystem().model.seed; + const url = new URL(`https://barthpaleologue.github.io/CosmosJourneyer/dist/random.html?seed=${seed}`); + navigator.clipboard.writeText(url.toString()); + }); this.canvas = document.getElementById("renderer") as HTMLCanvasElement; this.canvas.width = window.innerWidth; @@ -86,11 +97,15 @@ export class SpaceEngine { } ]); + window.addEventListener("blur", () => { + if (!this.isPaused()) this.pause(); + }); + //TODO: use the keyboard class document.addEventListener("keydown", (e) => { if (e.key === "o") OverlayPostProcess.ARE_ENABLED = !OverlayPostProcess.ARE_ENABLED; if (e.key === "n") this.orbitRenderer.setVisibility(!this.orbitRenderer.isVisible()); - if (e.key === "p") Tools.CreateScreenshot(this.getEngine(), this.getStarSystemScene().getActiveController().getActiveCamera(), { precision: 4 }); + if (e.key === "p") this.takeScreenshot(); if (e.key === "v") { if (!VideoRecorder.IsSupported(this.getEngine())) console.warn("Your browser does not support video recording!"); if (this.videoRecorder === null) { @@ -121,15 +136,36 @@ export class SpaceEngine { // when pressing f11, the ui is hidden when the browser is in fullscreen mode if (e.key === "F11") this.isFullscreen = !this.isFullscreen; + + if (e.key === "Escape") { + if (this.state === EngineState.RUNNING) this.pause(); + else this.resume(); + } }); } + takeScreenshot(): void { + const camera = this.getActiveScene().activeCamera; + if (camera === null) throw new Error("Cannot take screenshot: camera is null"); + Tools.CreateScreenshot(this.getEngine(), camera, { precision: 4 }); + } + pause(): void { this.state = EngineState.PAUSED; + this.pauseMenu.setVisibility(true); + this.getStarSystemScene().physicsEnabled = false; + this.getStarMap().setRunning(false); } resume(): void { this.state = EngineState.RUNNING; + this.pauseMenu.setVisibility(false); + this.getStarSystemScene().physicsEnabled = true; + this.getStarMap().setRunning(true); + } + + isPaused(): boolean { + return this.state === EngineState.PAUSED; } /** @@ -209,7 +245,7 @@ export class SpaceEngine { }); this.starSystemScene.registerBeforeRender(() => { - if (this.state === EngineState.PAUSED) return; + if (this.isPaused()) return; const starSystemScene = this.getStarSystemScene(); const starSystem = this.getStarSystem(); diff --git a/src/ts/index.ts b/src/ts/index.ts index c6ddcd707..f544e9098 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -51,16 +51,14 @@ spaceshipController.addInput(mouse); const physicsViewer = new PhysicsViewer(); //physicsViewer.showBody(spaceshipController.aggregate.body); -mouse.addOnMouseEnterListener(() => { - if (scene.getActiveController() === spaceshipController) engine.resume(); -}); -mouse.addOnMouseLeaveListener(() => { +mouse.onMouseLeaveObservable.add(() => { if (scene.getActiveController() === spaceshipController) engine.pause(); }); scene.setActiveController(spaceshipController); engine.registerStarSystemUpdateCallback(() => { + if (engine.isPaused()) return; if (scene.getActiveController() != spaceshipController) return; const shipPosition = spaceshipController.getTransform().getAbsolutePosition(); diff --git a/src/ts/randomizer.ts b/src/ts/randomizer.ts index ea0b9c4c5..01d387784 100644 --- a/src/ts/randomizer.ts +++ b/src/ts/randomizer.ts @@ -41,6 +41,7 @@ spaceshipController.addInput(gamepad); scene.setActiveController(spaceshipController); engine.registerStarSystemUpdateCallback(() => { + if (engine.isPaused()) return; if (scene.getActiveController() != spaceshipController) return; const shipPosition = spaceshipController.getTransform().getAbsolutePosition(); @@ -99,3 +100,5 @@ const nbRadius = starSystem.model.getBodyTypeOfStar(0) === BODY_TYPE.BLACK_HOLE positionNearObject(scene.getActiveController(), starSystem.planets.length > 0 ? starSystem.getBodies()[1] : starSystem.stellarObjects[0], starSystem, nbRadius); engine.bodyEditor.setVisibility(EditorVisibility.NAVBAR); + +engine.toggleStarMap(); diff --git a/src/ts/spaceship/abstractThruster.ts b/src/ts/spaceship/abstractThruster.ts index a335afc0c..fcb4284d0 100644 --- a/src/ts/spaceship/abstractThruster.ts +++ b/src/ts/spaceship/abstractThruster.ts @@ -1,5 +1,5 @@ import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial"; -import { Axis, Vector3 } from "@babylonjs/core/Maths/math"; +import { Vector3 } from "@babylonjs/core/Maths/math"; import { Color3 } from "@babylonjs/core/Maths/math.color"; import { AbstractMesh, MeshBuilder } from "@babylonjs/core/Meshes"; import { DirectionnalParticleSystem } from "../utils/particleSystem"; diff --git a/src/ts/starmap/starMap.ts b/src/ts/starmap/starMap.ts index 536fd363b..2c6fbddf5 100644 --- a/src/ts/starmap/starMap.ts +++ b/src/ts/starmap/starMap.ts @@ -39,6 +39,8 @@ export class StarMap { readonly scene: Scene; private readonly controller: PlayerController; + private isRunning: boolean = true; + private rotationAnimation: TransformRotationAnimation | null = null; private translationAnimation: TransformTranslationAnimation | null = null; @@ -218,7 +220,9 @@ export class StarMap { }); this.densityRNG = (x: number, y: number, z: number) => (1.0 - Math.abs(perlinRNG(x * 0.2, y * 0.2, z * 0.2))) ** 8; - this.scene.registerBeforeRender(() => { + this.scene.onBeforeRenderObservable.add(() => { + if(!this.isRunning) return; + const deltaTime = this.scene.getEngine().getDeltaTime() / 1000; if (this.rotationAnimation !== null) this.rotationAnimation.update(deltaTime); @@ -251,7 +255,11 @@ export class StarMap { }); } - public dispatchWarpCallbacks() { + public setRunning(running: boolean): void { + this.isRunning = running; + } + + private dispatchWarpCallbacks() { if (this.selectedSystemSeed === null) throw new Error("No system selected!"); this.onWarpObservable.notifyObservers(this.selectedSystemSeed); } diff --git a/src/ts/ui/pauseMenu.ts b/src/ts/ui/pauseMenu.ts new file mode 100644 index 000000000..058e18468 --- /dev/null +++ b/src/ts/ui/pauseMenu.ts @@ -0,0 +1,41 @@ +import { Observable } from "@babylonjs/core/Misc/observable"; +import pauseMenuHTML from "../../html/pauseMenu.html"; + +export class PauseMenu { + private readonly rootNode: HTMLElement; + private readonly mask: HTMLElement; + + private readonly screenshotButton: HTMLElement; + private readonly shareButton: HTMLElement; + private readonly resumeButton: HTMLElement; + + readonly onScreenshot = new Observable(); + readonly onShare = new Observable(); + readonly onResume = new Observable(); + + constructor() { + document.body.insertAdjacentHTML("beforeend", pauseMenuHTML); + this.rootNode = document.getElementById("pauseMenu") as HTMLElement; + this.mask = document.getElementById("pauseMask") as HTMLElement; + + this.screenshotButton = document.getElementById("screenshotButton") as HTMLElement; + this.screenshotButton.addEventListener("click", () => this.onScreenshot.notifyObservers()); + + this.shareButton = document.getElementById("shareButton") as HTMLElement; + this.shareButton.addEventListener("click", () => this.onShare.notifyObservers()); + + this.resumeButton = document.getElementById("resumeButton") as HTMLElement; + this.resumeButton.addEventListener("click", () => this.onResume.notifyObservers()); + + this.setVisibility(false); + } + + public setVisibility(visible: boolean) { + this.rootNode.style.display = visible ? "grid" : "none"; + this.mask.style.display = visible ? "block" : "none"; + } + + public isVisible(): boolean { + return this.rootNode.style.visibility !== "none"; + } +}