add back game over from chest
This commit is contained in:
@@ -102,6 +102,48 @@ canvas {
|
|||||||
font-weight: 600;
|
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 {
|
.control-panel {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
BIN
img/img_door.png
Normal file
BIN
img/img_door.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
BIN
img/img_jobapplication.png
Normal file
BIN
img/img_jobapplication.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
@@ -19,6 +19,11 @@
|
|||||||
<div class="canvas-hud-row"><span>Has key</span><strong id="canvas-key">no</strong></div>
|
<div class="canvas-hud-row"><span>Has key</span><strong id="canvas-key">no</strong></div>
|
||||||
<div class="canvas-hud-row"><span>Rounds</span><strong id="canvas-rounds">0</strong></div>
|
<div class="canvas-hud-row"><span>Rounds</span><strong id="canvas-rounds">0</strong></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="game-over-overlay" class="game-over-overlay" hidden>
|
||||||
|
<img id="game-over-image" alt="Game over" class="game-over-image" />
|
||||||
|
<div class="game-over-text">Game Over</div>
|
||||||
|
<div class="game-over-subtext">Press R to play again</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { playSfx, primeSfx } from "./sfx.js";
|
|||||||
import chestTextureUrl from "../img/img_chest.png";
|
import chestTextureUrl from "../img/img_chest.png";
|
||||||
import wallTextureUrl from "../img/img_wall.png";
|
import wallTextureUrl from "../img/img_wall.png";
|
||||||
import groundTextureUrl from "../img/img_ground.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
|
// Initialize Babylon.js engine and scene
|
||||||
const canvas = document.getElementById("renderCanvas");
|
const canvas = document.getElementById("renderCanvas");
|
||||||
@@ -13,6 +15,11 @@ const engine = new BABYLON.Engine(canvas, true);
|
|||||||
const canvasTime = document.getElementById("canvas-time");
|
const canvasTime = document.getElementById("canvas-time");
|
||||||
const canvasKey = document.getElementById("canvas-key");
|
const canvasKey = document.getElementById("canvas-key");
|
||||||
const canvasRounds = document.getElementById("canvas-rounds");
|
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);
|
const scene = new BABYLON.Scene(engine);
|
||||||
scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);
|
scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);
|
||||||
@@ -49,6 +56,7 @@ scene.activeCamera = camera;
|
|||||||
let lastFootstepPosition = null;
|
let lastFootstepPosition = null;
|
||||||
let footstepAccumulator = 0;
|
let footstepAccumulator = 0;
|
||||||
let footstepElapsed = 0;
|
let footstepElapsed = 0;
|
||||||
|
let gameOverActive = false;
|
||||||
|
|
||||||
scene.gravity = new BABYLON.Vector3(0, -0.2, 0);
|
scene.gravity = new BABYLON.Vector3(0, -0.2, 0);
|
||||||
scene.collisionsEnabled = true;
|
scene.collisionsEnabled = true;
|
||||||
@@ -66,6 +74,34 @@ function updateOverviewCameraForMaze(w, h) {
|
|||||||
overviewCamera.target = new BABYLON.Vector3(0, 0, 0);
|
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() {
|
function switchCameraMode() {
|
||||||
if (cameraMode === "fp") {
|
if (cameraMode === "fp") {
|
||||||
if (document.pointerLockElement === canvas && document.exitPointerLock) {
|
if (document.pointerLockElement === canvas && document.exitPointerLock) {
|
||||||
@@ -87,9 +123,13 @@ function switchCameraMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", (event) => {
|
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();
|
primeSfx();
|
||||||
}
|
}
|
||||||
|
if (event.code === "KeyR" && gameOverActive) {
|
||||||
|
restartRunFromGameOver();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.code === "KeyV") {
|
if (event.code === "KeyV") {
|
||||||
switchCameraMode();
|
switchCameraMode();
|
||||||
}
|
}
|
||||||
@@ -280,39 +320,21 @@ function placeExit(grid, seed) {
|
|||||||
const exitWorld = gridCellToWorld(grid, x, y, cellSize);
|
const exitWorld = gridCellToWorld(grid, x, y, cellSize);
|
||||||
const ex = exitWorld.x;
|
const ex = exitWorld.x;
|
||||||
const ez = exitWorld.z;
|
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.CreatePlane('exitDoor', {
|
||||||
const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene);
|
width: cellSize * 1.35,
|
||||||
|
height: cellSize * 1.85,
|
||||||
|
sideOrientation: BABYLON.Mesh.DOUBLESIDE,
|
||||||
|
}, scene);
|
||||||
const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
|
const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
|
||||||
exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03);
|
exitMat.diffuseTexture = new BABYLON.Texture(doorTextureUrl, scene);
|
||||||
exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15);
|
exitMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);
|
||||||
exitMat.disableLighting = true;
|
exitMat.emissiveColor = new BABYLON.Color3(0.07, 0.07, 0.07);
|
||||||
plane.material = exitMat;
|
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;
|
exitBox = plane;
|
||||||
levelMeshes.push(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) {
|
function spawnCameraAt(grid) {
|
||||||
@@ -368,6 +390,7 @@ function spawnCameraAt(grid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateLevel() {
|
function generateLevel() {
|
||||||
|
hideGameOverScreen();
|
||||||
const cfg = sharedState.config;
|
const cfg = sharedState.config;
|
||||||
const seed = cfg.seed;
|
const seed = cfg.seed;
|
||||||
const w = Math.max(9, cfg.mazeWidth);
|
const w = Math.max(9, cfg.mazeWidth);
|
||||||
@@ -415,8 +438,10 @@ scene.onPointerObservable.add((pi) => {
|
|||||||
if (entry.opened) {
|
if (entry.opened) {
|
||||||
primeSfx();
|
primeSfx();
|
||||||
playSfx("chestClose", 0.8);
|
playSfx("chestClose", 0.8);
|
||||||
|
playSfx("lose", 0.85);
|
||||||
sharedState.runtime.runActive = false;
|
sharedState.runtime.runActive = false;
|
||||||
sharedState.runtime.message = 'Opened chest again — game over.';
|
sharedState.runtime.message = 'Opened chest again — game over.';
|
||||||
|
showGameOverScreen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
primeSfx();
|
primeSfx();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const soundFiles = {
|
|||||||
chestOpen: "/sfx/sfx_chest_open.wav",
|
chestOpen: "/sfx/sfx_chest_open.wav",
|
||||||
click: "/sfx/sfx_click.wav",
|
click: "/sfx/sfx_click.wav",
|
||||||
key: "/sfx/sfx_key.wav",
|
key: "/sfx/sfx_key.wav",
|
||||||
|
lose: "/sfx/sfx_lose.wav",
|
||||||
step: "/sfx/sfx_step.wav",
|
step: "/sfx/sfx_step.wav",
|
||||||
win: "/sfx/sfx_win.wav",
|
win: "/sfx/sfx_win.wav",
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user