Add shared UI overlay (load, title, HUD, pause, gameover, highscore)
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/* ============================================================
|
||||
DSP ARCADE — shared UI: load screen, title, HUD, pause,
|
||||
game over, high-score entry. Draws on top of the game canvas.
|
||||
============================================================ */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const { STATE } = window.DSP;
|
||||
|
||||
function text(ctx, str, x, y, size, color, align = 'center') {
|
||||
ctx.font = `${size}px "Press Start 2P", monospace`;
|
||||
ctx.textAlign = align;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillText(str, x, y);
|
||||
}
|
||||
|
||||
function glowText(ctx, str, x, y, size, color, glow) {
|
||||
ctx.save();
|
||||
ctx.shadowColor = glow || color;
|
||||
ctx.shadowBlur = 12;
|
||||
text(ctx, str, x, y, size, color);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function blink(on) { return Math.floor(performance.now() / 400) % 2 === on; }
|
||||
|
||||
function draw(game) {
|
||||
const ctx = game.ctx;
|
||||
const W = game.W, H = game.H;
|
||||
const cx = W / 2;
|
||||
|
||||
switch (game.state) {
|
||||
case STATE.LOADING: {
|
||||
ctx.fillStyle = '#05010d';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const sx = (i * 97 + game.time * 0.3) % W;
|
||||
const sy = (i * 53) % H;
|
||||
ctx.fillStyle = i % 3 === 0 ? '#ff2d95' : '#7df9ff';
|
||||
ctx.globalAlpha = 0.3 + (i % 5) * 0.12;
|
||||
ctx.fillRect(sx, sy, 2, 2);
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
glowText(ctx, game.title, cx, H * 0.3, 20, '#ff2d95');
|
||||
const step = game.loadSteps[Math.min(game.loadStep, game.loadSteps.length - 1)];
|
||||
text(ctx, step + '...', cx, H * 0.5, 8, '#7df9ff');
|
||||
const bw = W * 0.5, bh = 14;
|
||||
const bx = cx - bw / 2, by = H * 0.6;
|
||||
ctx.strokeStyle = '#7df9ff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(bx, by, bw, bh);
|
||||
ctx.fillStyle = '#ff2d95';
|
||||
ctx.fillRect(bx + 2, by + 2, (bw - 4) * Math.min(1, game.loadProgress), bh - 4);
|
||||
text(ctx, Math.floor(Math.min(1, game.loadProgress) * 100) + '%', cx, by + bh + 16, 8, '#ffffff');
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE.TITLE: {
|
||||
ctx.fillStyle = 'rgba(5,1,13,0.75)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
glowText(ctx, game.title, cx, H * 0.28, 22, '#ff2d95');
|
||||
text(ctx, 'DIGITAL SPACEPORT ARCADE', cx, H * 0.28 + 30, 7, '#7df9ff');
|
||||
if (blink(0)) text(ctx, 'PRESS SPACE TO START', cx, H * 0.55, 10, '#ffe14d');
|
||||
text(ctx, 'HI-SCORE: ' + String(game.best).padStart(6, '0'), cx, H * 0.68, 8, '#7dff5e');
|
||||
text(ctx, 'M: MUTE P: PAUSE', cx, H * 0.85, 7, '#8a7fb5');
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE.PLAYING:
|
||||
case STATE.PAUSED: {
|
||||
ctx.fillStyle = 'rgba(5,1,13,0.55)';
|
||||
ctx.fillRect(0, 0, W, 26);
|
||||
text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), 8, 13, 8, '#ffffff', 'left');
|
||||
text(ctx, 'HI ' + String(game.best).padStart(6, '0'), W / 2, 13, 8, '#7dff5e');
|
||||
text(ctx, 'LV ' + game.level, W - 8, 13, 8, '#ffe14d', 'right');
|
||||
const hearts = Math.min(game.lives, 5);
|
||||
for (let i = 0; i < hearts; i++) {
|
||||
const img = window.DSP_SPRITES.get('heart', 2);
|
||||
ctx.drawImage(img, 8 + i * 18, 32);
|
||||
}
|
||||
if (game.mouseCaptured !== undefined) {
|
||||
if (game.mouseCaptured) {
|
||||
text(ctx, 'MOUS FANGEN - ESC FREIGIBT', W - 8, 44, 7, '#ff7de9', 'right');
|
||||
} else if (game.state === STATE.PLAYING) {
|
||||
text(ctx, 'KLICK IN CANVAS = MAUS FANGEN', W - 8, 44, 7, '#8a7fb5', 'right');
|
||||
}
|
||||
}
|
||||
if (game.state === STATE.PAUSED) {
|
||||
ctx.fillStyle = 'rgba(5,1,13,0.7)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
glowText(ctx, 'PAUSED', cx, H * 0.45, 20, '#7df9ff');
|
||||
text(ctx, 'PRESS P TO RESUME', cx, H * 0.58, 8, '#ffffff');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE.GAMEOVER: {
|
||||
ctx.fillStyle = 'rgba(5,1,13,0.8)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
glowText(ctx, 'GAME OVER', cx, H * 0.3, 22, '#ff4d6d');
|
||||
text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), cx, H * 0.45, 10, '#ffffff');
|
||||
if (game.gameOverRank > 0) {
|
||||
text(ctx, 'RANK #' + game.gameOverRank + ' ON THE BOARD', cx, H * 0.55, 8, '#ffe14d');
|
||||
}
|
||||
if (blink(0)) text(ctx, 'PRESS SPACE', cx, H * 0.7, 9, '#7df9ff');
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE.HIGHSCORE: {
|
||||
ctx.fillStyle = 'rgba(5,1,13,0.85)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
glowText(ctx, 'NEW HIGH SCORE!', cx, H * 0.25, 14, '#ffe14d');
|
||||
text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), cx, H * 0.38, 10, '#ffffff');
|
||||
text(ctx, 'ENTER PILOT NAME', cx, H * 0.5, 8, '#7df9ff');
|
||||
const name = (game._hsName || '').toUpperCase();
|
||||
const cursor = blink(0) ? '_' : ' ';
|
||||
text(ctx, (name + cursor).padStart(9, ' '), cx, H * 0.6, 14, '#7dff5e');
|
||||
text(ctx, 'TYPE NAME + ENTER', cx, H * 0.72, 7, '#8a7fb5');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
const g = window.DSP_ACTIVE_GAME;
|
||||
if (!g || g.state !== STATE.HIGHSCORE) return;
|
||||
if (e.code === 'Enter') return;
|
||||
if (e.code === 'Backspace') {
|
||||
g._hsName = (g._hsName || '').slice(0, -1);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key && e.key.length === 1 && /[a-zA-Z]/.test(e.key)) {
|
||||
g._hsName = ((g._hsName || '') + e.key).slice(0, 8);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
window.DSP_UI = { draw };
|
||||
})();
|
||||
Reference in New Issue
Block a user