use timer based mechanic instead
This commit is contained in:
@@ -78,6 +78,22 @@ canvas {
|
||||
line-height: 1;
|
||||
color: #eef5ff;
|
||||
text-shadow: 0 0 12px rgba(121, 174, 242, 0.35);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.canvas-hud-value.low-time {
|
||||
color: #ff4444;
|
||||
text-shadow: 0 0 16px rgba(255, 68, 68, 0.6);
|
||||
animation: pulse-warning 0.6s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-warning {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.canvas-hud-row {
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
<body class="maze-page">
|
||||
<main class="maze-layout">
|
||||
<section class="panel">
|
||||
<div class="panel-label">Babylon Scene</div>
|
||||
<div class="panel-label">UNTITLED MAZE GAME</div>
|
||||
<div class="canvas-stage">
|
||||
<canvas id="renderCanvas"></canvas>
|
||||
<div class="canvas-hud">
|
||||
<div class="canvas-hud-label">Elapsed time</div>
|
||||
<div class="canvas-hud-label">Time left</div>
|
||||
<div id="canvas-time" class="canvas-hud-value">0.0s</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>
|
||||
@@ -66,7 +66,9 @@
|
||||
<div class="status-display">
|
||||
<div class="status-line"><strong>Seed:</strong> <span id="status-seed">0</span></div>
|
||||
<div class="status-line"><strong>Level:</strong> <span id="status-level">1</span></div>
|
||||
<div class="status-line"><strong>Time:</strong> <span id="status-time">0.0</span></div>
|
||||
<div class="status-line"><strong>Maze:</strong> <span id="status-maze-size">11x11</span></div>
|
||||
<div class="status-line"><strong>Chests:</strong> <span id="status-chests">2</span></div>
|
||||
<div class="status-line"><strong>Time left:</strong> <span id="status-time">60.0</span></div>
|
||||
<div class="status-line"><strong>Key:</strong> <span id="status-key">no</span></div>
|
||||
<div class="status-line"><strong>Rounds:</strong> <span id="status-rounds">0</span></div>
|
||||
<div class="status-message" id="status-message">Adjust settings, then start a run.</div>
|
||||
|
||||
@@ -57,6 +57,7 @@ let lastFootstepPosition = null;
|
||||
let footstepAccumulator = 0;
|
||||
let footstepElapsed = 0;
|
||||
let gameOverActive = false;
|
||||
let lowTimeAlertPlayed = false;
|
||||
|
||||
scene.gravity = new BABYLON.Vector3(0, -0.2, 0);
|
||||
scene.collisionsEnabled = true;
|
||||
@@ -95,7 +96,7 @@ function restartRunFromGameOver() {
|
||||
sharedState.runtime.runActive = true;
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.roundsCompleted = 0;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.elapsedSeconds = ROUND_TIME_SECONDS;
|
||||
sharedState.runtime.message = "Run restarted.";
|
||||
sharedState.config.level = 1;
|
||||
hideGameOverScreen();
|
||||
@@ -179,6 +180,7 @@ let spawnGridPos = null; // track spawn grid position for validation
|
||||
let spawnMarker = null;
|
||||
let highlightedChest = null;
|
||||
const cellSize = 2;
|
||||
const ROUND_TIME_SECONDS = 60;
|
||||
|
||||
function setChestHighlight(mesh) {
|
||||
if (highlightedChest === mesh) {
|
||||
@@ -391,15 +393,21 @@ function spawnCameraAt(grid) {
|
||||
|
||||
function generateLevel() {
|
||||
hideGameOverScreen();
|
||||
lowTimeAlertPlayed = false;
|
||||
if (canvasTime) {
|
||||
canvasTime.classList.remove("low-time");
|
||||
}
|
||||
const cfg = sharedState.config;
|
||||
const seed = cfg.seed;
|
||||
const w = Math.max(9, cfg.mazeWidth);
|
||||
const h = Math.max(9, cfg.mazeHeight);
|
||||
const roundScale = Math.max(0, cfg.level - 1);
|
||||
const w = Math.max(9, cfg.mazeWidth + roundScale * 2);
|
||||
const h = Math.max(9, cfg.mazeHeight + roundScale * 2);
|
||||
const chestCount = Math.max(1, cfg.minChestDeadEnds + roundScale);
|
||||
const grid = generateMazeGrid(w, h, seed + cfg.level);
|
||||
updateOverviewCameraForMaze(w, h);
|
||||
const dead = findDeadEnds(grid);
|
||||
buildLevelFromGrid(grid);
|
||||
placeChestsOnDeadEnds(grid, dead, cfg.minChestDeadEnds, seed + cfg.level);
|
||||
placeChestsOnDeadEnds(grid, dead, chestCount, seed + cfg.level);
|
||||
placeExit(grid, seed + cfg.level);
|
||||
spawnCameraAt(grid);
|
||||
|
||||
@@ -428,6 +436,7 @@ window.mazeGameApi = { generateLevel };
|
||||
// Pointer interaction for chests
|
||||
scene.onPointerObservable.add((pi) => {
|
||||
if (pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) return;
|
||||
if (!sharedState.runtime.runActive || gameOverActive) return;
|
||||
const pick = scene.pick(scene.pointerX, scene.pointerY);
|
||||
if (!pick || !pick.hit || !pick.pickedMesh) return;
|
||||
const m = pick.pickedMesh;
|
||||
@@ -467,7 +476,31 @@ scene.registerBeforeRender(() => {
|
||||
}
|
||||
|
||||
if (sharedState.runtime.runActive) {
|
||||
sharedState.runtime.elapsedSeconds += engine.getDeltaTime() / 1000;
|
||||
const dt = engine.getDeltaTime() / 1000;
|
||||
sharedState.runtime.elapsedSeconds = Math.max(0, sharedState.runtime.elapsedSeconds - dt);
|
||||
|
||||
const isLowTime = sharedState.runtime.elapsedSeconds < 10;
|
||||
if (isLowTime && !lowTimeAlertPlayed) {
|
||||
lowTimeAlertPlayed = true;
|
||||
playSfx("clock", 0.75);
|
||||
if (canvasTime) {
|
||||
canvasTime.classList.add("low-time");
|
||||
}
|
||||
}
|
||||
if (!isLowTime && lowTimeAlertPlayed) {
|
||||
lowTimeAlertPlayed = false;
|
||||
if (canvasTime) {
|
||||
canvasTime.classList.remove("low-time");
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedState.runtime.elapsedSeconds <= 0) {
|
||||
sharedState.runtime.runActive = false;
|
||||
sharedState.runtime.message = "Time up — game over.";
|
||||
playSfx("lose", 0.85);
|
||||
showGameOverScreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sharedState.runtime.runActive && cameraMode === "fp" && camera && camera.position && document.pointerLockElement === canvas) {
|
||||
const currentPosition = camera.position;
|
||||
@@ -496,7 +529,7 @@ scene.registerBeforeRender(() => {
|
||||
sharedState.config.level += 1;
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.roundsCompleted += 1;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.elapsedSeconds = ROUND_TIME_SECONDS;
|
||||
sharedState.runtime.message = `Level ${sharedState.config.level} starting.`;
|
||||
generateLevel();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const sharedState = (window.mazeGameState ??= {
|
||||
runActive: false,
|
||||
hasKey: false,
|
||||
roundsCompleted: 0,
|
||||
elapsedSeconds: 0,
|
||||
elapsedSeconds: 60,
|
||||
message: "Adjust settings, then start a run.",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ function resetRun(message) {
|
||||
sharedState.runtime.runActive = true;
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.roundsCompleted = 0;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.elapsedSeconds = 60;
|
||||
sharedState.runtime.message = message;
|
||||
sharedState.config.level = 1;
|
||||
try { window.mazeGameApi.generateLevel(); } catch (e) { console.warn(e); }
|
||||
@@ -15,7 +15,7 @@ function resetRun(message) {
|
||||
|
||||
function restartLevel(message) {
|
||||
sharedState.runtime.hasKey = false;
|
||||
sharedState.runtime.elapsedSeconds = 0;
|
||||
sharedState.runtime.elapsedSeconds = 60;
|
||||
sharedState.runtime.message = message;
|
||||
try { window.mazeGameApi.generateLevel(); } catch (e) { console.warn(e); }
|
||||
updateDisplay();
|
||||
@@ -33,8 +33,16 @@ function updateDisplay() {
|
||||
document.getElementById("value-height").textContent = sharedState.config.mazeHeight;
|
||||
document.getElementById("value-deadends").textContent = sharedState.config.minChestDeadEnds;
|
||||
|
||||
// Calculate effective maze size and chest count based on current level
|
||||
const roundScale = Math.max(0, sharedState.config.level - 1);
|
||||
const effectiveWidth = Math.max(9, sharedState.config.mazeWidth + roundScale * 2);
|
||||
const effectiveHeight = Math.max(9, sharedState.config.mazeHeight + roundScale * 2);
|
||||
const effectiveChests = Math.max(1, sharedState.config.minChestDeadEnds + roundScale);
|
||||
|
||||
document.getElementById("status-seed").textContent = sharedState.config.seed;
|
||||
document.getElementById("status-level").textContent = sharedState.config.level;
|
||||
document.getElementById("status-maze-size").textContent = `${effectiveWidth}x${effectiveHeight}`;
|
||||
document.getElementById("status-chests").textContent = effectiveChests;
|
||||
document.getElementById("status-time").textContent = sharedState.runtime.elapsedSeconds.toFixed(1);
|
||||
document.getElementById("status-key").textContent = sharedState.runtime.hasKey ? "yes" : "no";
|
||||
document.getElementById("status-rounds").textContent = sharedState.runtime.roundsCompleted;
|
||||
@@ -84,6 +92,13 @@ document.getElementById("slider-deadends").addEventListener("input", (e) => {
|
||||
// Update status display on game loop
|
||||
setInterval(() => {
|
||||
if (sharedState.runtime.runActive) {
|
||||
const roundScale = Math.max(0, sharedState.config.level - 1);
|
||||
const effectiveWidth = Math.max(9, sharedState.config.mazeWidth + roundScale * 2);
|
||||
const effectiveHeight = Math.max(9, sharedState.config.mazeHeight + roundScale * 2);
|
||||
const effectiveChests = Math.max(1, sharedState.config.minChestDeadEnds + roundScale);
|
||||
|
||||
document.getElementById("status-maze-size").textContent = `${effectiveWidth}x${effectiveHeight}`;
|
||||
document.getElementById("status-chests").textContent = effectiveChests;
|
||||
document.getElementById("status-time").textContent = sharedState.runtime.elapsedSeconds.toFixed(1);
|
||||
document.getElementById("status-key").textContent = sharedState.runtime.hasKey ? "yes" : "no";
|
||||
document.getElementById("status-rounds").textContent = sharedState.runtime.roundsCompleted;
|
||||
|
||||
@@ -2,6 +2,7 @@ const soundFiles = {
|
||||
chestClose: "/sfx/sfx_chest_close.wav",
|
||||
chestOpen: "/sfx/sfx_chest_open.wav",
|
||||
click: "/sfx/sfx_click.wav",
|
||||
clock: "/sfx/sfx_clock.wav",
|
||||
key: "/sfx/sfx_key.wav",
|
||||
lose: "/sfx/sfx_lose.wav",
|
||||
step: "/sfx/sfx_step.wav",
|
||||
|
||||
Reference in New Issue
Block a user