Put a menu in front of the game, with Marvin to explain it
The overlay is a four-stage machine now — splash -> menu -> {courts, tutorial}
— swapped by data-stage while the backdrop and card frame stay mounted, so the
live court never flashes through between screens.
Court tiles SELECT only. Tapping one used to start the match on the spot, which
gave you no way to compare the three and no way back out; START GAME and BACK
are their own buttons now. Headlines are 15px against the 11px body, so a screen
reads as a screen rather than as a list.
The tutorial is a data table: a Marvin pose, an optional picture already in
assets/, and his lines. Seven pages covering tennis, the turn, the slingshot,
the rack and the duel. Marvin is four new frames off x_boymop1 — waving,
pointing, shrugging, and clutching his mop in terror.
The theme no longer starts at boot. A title screen that plays at you before you
have touched it is rude, and the autoplay policy would have held it silent until
that first tap anyway.
Death returns to the menu instead of the title. The reload stays — it is the
honest full reset — and a sessionStorage flag is what picks the stage on the way
back in.
Machine-gun rounds are tennis balls, so they are drawn as balls and take the
perspective. A fixed 4x4 fillRect was exactly as big at the far baseline as at
your feet, which read as square confetti pinned to the screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60f7ce2cf1
commit
9a65a50cc4
10 files changed
+199
-44
No files matched your search
@@ -151,7 +151,7 @@ export function proj(x, y, z) {
|
||||
|
||||
// ---------------------------------------------------------------- sprites ----
|
||||
// assets/<name>.png; game falls back to code-drawn figures until sprites load.
|
||||
const SPRV = 77; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPRV = 78; // asset cache-buster — bump with main.js?v=N (see the img.src note below)
|
||||
const SPR = {};
|
||||
for (const n of ['p_idle1','p_idle2','p_bounce','p_bounce2','p_toss','p_smash','p_run1','p_run2','p_run3','p_run4','p_swing1','p_swing2','p_die1','p_die2','p_die3','p_die4','p_die5',
|
||||
'p_pull', // the serve wind-up: the racket dropping behind him while the pull is drawn
|
||||
@@ -954,11 +954,15 @@ export function render() {
|
||||
: pt.dark ? 'rgba(96,14,14,0.9)' : 'rgba(170,22,22,0.9)';
|
||||
ctx.fillRect(pp.x | 0, pp.y | 0, 2, 2);
|
||||
}
|
||||
// machine-gun pellets: flat tracers with a tiny ground shadow
|
||||
// Machine-gun rounds: they are TENNIS BALLS, fired small and hard, so they are drawn as balls and
|
||||
// they take the perspective like everything else. A fixed 4x4 fillRect stayed exactly as big at
|
||||
// the far baseline as at your feet, which read as square confetti pinned to the screen rather than
|
||||
// as something travelling away from you. Radius is a shade over half the match ball's 0.09.
|
||||
for (const pe of pellets) {
|
||||
const sh = proj(pe.x, 0, pe.z); drawShadow(sh.x, sh.y, Math.max(1, sh.s * 0.04), Math.max(1, sh.s * 0.02), 0.25);
|
||||
const pp = proj(pe.x, pe.y, pe.z);
|
||||
ctx.fillStyle = UI.warm; ctx.fillRect((pp.x | 0) - 2, (pp.y | 0) - 2, 4, 4); // fat round — it hits like one
|
||||
const pp = proj(pe.x, pe.y, pe.z), r = Math.max(1, pp.s * 0.05);
|
||||
ctx.fillStyle = UI.ball; ctx.beginPath(); ctx.arc(pp.x, pp.y, r, 0, 7); ctx.fill();
|
||||
ctx.fillStyle = '#fffbe0'; ctx.beginPath(); ctx.arc(pp.x, pp.y, Math.max(0.5, r * 0.45), 0, 7); ctx.fill(); // hot core: it reads sharp, not soft
|
||||
}
|
||||
// the chapter boss's shockwave: a ring of blood mist blown out through the crowd noise
|
||||
if (G.bossBurst) {
|
||||
|
||||
Reference in new issue
Block a user