diff --git a/css/style.css b/css/style.css index 6a05b72..a677bf5 100644 --- a/css/style.css +++ b/css/style.css @@ -102,6 +102,48 @@ canvas { font-weight: 600; } +.game-over-overlay { + position: absolute; + inset: 0; + z-index: 3; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + background: rgba(0, 0, 0, 0.78); + text-align: center; + padding: 20px; +} + +.game-over-overlay[hidden] { + display: none; +} + +.game-over-image { + width: min(70%, 460px); + max-height: 52vh; + object-fit: contain; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.22); + box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45); +} + +.game-over-text { + margin-top: 4px; + font-size: clamp(24px, 4vw, 40px); + font-weight: 700; + color: #f3f7ff; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.game-over-subtext { + font-size: 14px; + color: #c9d8ea; + letter-spacing: 0.05em; +} + .control-panel { padding: 16px; overflow-y: auto; diff --git a/img/img_door.png b/img/img_door.png new file mode 100644 index 0000000..6146e59 Binary files /dev/null and b/img/img_door.png differ diff --git a/img/img_jobapplication.png b/img/img_jobapplication.png new file mode 100644 index 0000000..f85f41c Binary files /dev/null and b/img/img_jobapplication.png differ diff --git a/index.html b/index.html index 03659c7..2b0fd5b 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,11 @@
Has keyno
Rounds0
+ diff --git a/src/babylon_setup.js b/src/babylon_setup.js index 69fdab7..b832441 100644 --- a/src/babylon_setup.js +++ b/src/babylon_setup.js @@ -6,6 +6,8 @@ import { playSfx, primeSfx } from "./sfx.js"; import chestTextureUrl from "../img/img_chest.png"; import wallTextureUrl from "../img/img_wall.png"; import groundTextureUrl from "../img/img_ground.png"; +import doorTextureUrl from "../img/img_door.png"; +import gameOverImageUrl from "../img/img_jobapplication.png"; // Initialize Babylon.js engine and scene const canvas = document.getElementById("renderCanvas"); @@ -13,6 +15,11 @@ const engine = new BABYLON.Engine(canvas, true); const canvasTime = document.getElementById("canvas-time"); const canvasKey = document.getElementById("canvas-key"); const canvasRounds = document.getElementById("canvas-rounds"); +const gameOverOverlay = document.getElementById("game-over-overlay"); +const gameOverImage = document.getElementById("game-over-image"); +if (gameOverImage) { + gameOverImage.src = gameOverImageUrl; +} const scene = new BABYLON.Scene(engine); scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1); @@ -49,6 +56,7 @@ scene.activeCamera = camera; let lastFootstepPosition = null; let footstepAccumulator = 0; let footstepElapsed = 0; +let gameOverActive = false; scene.gravity = new BABYLON.Vector3(0, -0.2, 0); scene.collisionsEnabled = true; @@ -66,6 +74,34 @@ function updateOverviewCameraForMaze(w, h) { overviewCamera.target = new BABYLON.Vector3(0, 0, 0); } +function showGameOverScreen() { + gameOverActive = true; + if (gameOverOverlay) { + gameOverOverlay.hidden = false; + } + if (document.pointerLockElement === canvas && document.exitPointerLock) { + document.exitPointerLock(); + } +} + +function hideGameOverScreen() { + gameOverActive = false; + if (gameOverOverlay) { + gameOverOverlay.hidden = true; + } +} + +function restartRunFromGameOver() { + sharedState.runtime.runActive = true; + sharedState.runtime.hasKey = false; + sharedState.runtime.roundsCompleted = 0; + sharedState.runtime.elapsedSeconds = 0; + sharedState.runtime.message = "Run restarted."; + sharedState.config.level = 1; + hideGameOverScreen(); + generateLevel(); +} + function switchCameraMode() { if (cameraMode === "fp") { if (document.pointerLockElement === canvas && document.exitPointerLock) { @@ -87,9 +123,13 @@ function switchCameraMode() { } window.addEventListener("keydown", (event) => { - if (event.code === "KeyW" || event.code === "KeyA" || event.code === "KeyS" || event.code === "KeyD" || event.code === "KeyV") { + if (event.code === "KeyW" || event.code === "KeyA" || event.code === "KeyS" || event.code === "KeyD" || event.code === "KeyV" || event.code === "KeyR") { primeSfx(); } + if (event.code === "KeyR" && gameOverActive) { + restartRunFromGameOver(); + return; + } if (event.code === "KeyV") { switchCameraMode(); } @@ -280,39 +320,21 @@ function placeExit(grid, seed) { const exitWorld = gridCellToWorld(grid, x, y, cellSize); const ex = exitWorld.x; const ez = exitWorld.z; - const worldSpan = Math.max(grid[0].length, grid.length) * cellSize; - - // Ground plane indicator (larger + slightly raised to avoid z-fighting) - const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene); + + const plane = BABYLON.MeshBuilder.CreatePlane('exitDoor', { + width: cellSize * 1.35, + height: cellSize * 1.85, + sideOrientation: BABYLON.Mesh.DOUBLESIDE, + }, scene); const exitMat = new BABYLON.StandardMaterial('exitMat', scene); - exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03); - exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15); - exitMat.disableLighting = true; + exitMat.diffuseTexture = new BABYLON.Texture(doorTextureUrl, scene); + exitMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95); + exitMat.emissiveColor = new BABYLON.Color3(0.07, 0.07, 0.07); plane.material = exitMat; - plane.position = new BABYLON.Vector3(ex, 0.08, ez); + plane.position = new BABYLON.Vector3(ex, cellSize * 0.92, ez); + plane.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y; exitBox = plane; levelMeshes.push(plane); - - // Very tall beacon for visibility over maze walls - const beaconHeight = Math.max(18, worldSpan * 0.8); - const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.55, height: beaconHeight }, scene); - const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene); - pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1); - pillarMat.emissiveColor = new BABYLON.Color3(1.0, 0.7, 0.1); - pillarMat.disableLighting = true; - pillar.material = pillarMat; - pillar.position = new BABYLON.Vector3(ex, beaconHeight * 0.5, ez); - levelMeshes.push(pillar); - - // Bright orb at the top of beacon - const orb = BABYLON.MeshBuilder.CreateSphere('exitOrb', { diameter: cellSize * 1.0 }, scene); - const orbMat = new BABYLON.StandardMaterial('exitOrbMat', scene); - orbMat.diffuseColor = new BABYLON.Color3(1.0, 0.9, 0.3); - orbMat.emissiveColor = new BABYLON.Color3(1.0, 0.9, 0.35); - orbMat.disableLighting = true; - orb.material = orbMat; - orb.position = new BABYLON.Vector3(ex, beaconHeight + cellSize * 0.7, ez); - levelMeshes.push(orb); } function spawnCameraAt(grid) { @@ -368,6 +390,7 @@ function spawnCameraAt(grid) { } function generateLevel() { + hideGameOverScreen(); const cfg = sharedState.config; const seed = cfg.seed; const w = Math.max(9, cfg.mazeWidth); @@ -415,8 +438,10 @@ scene.onPointerObservable.add((pi) => { if (entry.opened) { primeSfx(); playSfx("chestClose", 0.8); + playSfx("lose", 0.85); sharedState.runtime.runActive = false; sharedState.runtime.message = 'Opened chest again — game over.'; + showGameOverScreen(); return; } primeSfx(); diff --git a/src/sfx.js b/src/sfx.js index 447564e..7fc9c18 100644 --- a/src/sfx.js +++ b/src/sfx.js @@ -3,6 +3,7 @@ const soundFiles = { chestOpen: "/sfx/sfx_chest_open.wav", click: "/sfx/sfx_click.wav", key: "/sfx/sfx_key.wav", + lose: "/sfx/sfx_lose.wav", step: "/sfx/sfx_step.wav", win: "/sfx/sfx_win.wav", };