Sprint 1: polish — sound, pause, tutorial, touch UI

- SoundManager via Web Audio API (no asset files needed) with procedural
  SFX for jump, spring, powerup, stomp, break, death, milestone, new-best
- Sound persists mute state in localStorage; mute button on Menu and Game
- Pause system: ESC key or onscreen pause button, modal overlay with
  Resume and Main Menu options, physics correctly paused/resumed
- First-run tutorial overlay explaining controls and platform types,
  dismissed and remembered via localStorage flag
- Touch indicator hints fade after 3.5s on touch devices only
- Menu start triggers AudioContext initialization (browser autoplay rules)
- GameOverScene supports ENTER/SPACE shortcut for retry, NEW BEST text
  now pulses, sounds fire on each transition
This commit is contained in:
2026-05-23 16:55:05 +07:00
parent d3f880d917
commit fd93da0a71
4 changed files with 383 additions and 55 deletions

View File

@@ -1,4 +1,5 @@
import { Scene } from 'phaser';
import { sound } from '../managers/SoundManager.js';
export class MenuScene extends Scene {
constructor() {
@@ -8,10 +9,8 @@ export class MenuScene extends Scene {
create() {
const { width, height } = this.scale;
// Background
this.add.tileSprite(width / 2, height / 2, width, height, 'gridBg');
// Title
this.add.text(width / 2, height * 0.18, 'NADDIE JUMP', {
fontFamily: '"Press Start 2P", monospace',
fontSize: '38px',
@@ -26,51 +25,27 @@ export class MenuScene extends Scene {
align: 'center',
}).setOrigin(0.5);
// Floating Naddie preview
const preview = this.add.image(width / 2, height * 0.48, 'player_idle')
.setScale(0.55);
this.tweens.add({
targets: preview,
y: height * 0.48 - 15,
duration: 1400,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
});
this.tweens.add({
targets: preview,
angle: 5,
duration: 2000,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
});
const preview = this.add.image(width / 2, height * 0.48, 'player_idle').setScale(0.55);
this.tweens.add({ targets: preview, y: height * 0.48 - 15, duration: 1400, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' });
this.tweens.add({ targets: preview, angle: 5, duration: 2000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' });
// Start button
this.createButton(width / 2, height * 0.68, 'START GAME', () => {
this.scene.start('GameScene');
});
this.createButton(width / 2, height * 0.68, 'START GAME', () => this.startGame());
this.add.text(width / 2, height * 0.74, 'Press ENTER or SPACE to start', {
this.add.text(width / 2, height * 0.74, 'ENTER / SPACE / TAP', {
fontFamily: '"Press Start 2P", monospace',
fontSize: '10px',
color: '#888',
align: 'center',
}).setOrigin(0.5);
this.input.keyboard.on('keydown-ENTER', () => {
this.scene.start('GameScene');
});
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('GameScene');
});
this.input.keyboard.on('keydown-ENTER', () => this.startGame());
this.input.keyboard.on('keydown-SPACE', () => this.startGame());
// Leaderboard button
this.createButton(width / 2, height * 0.78, 'LEADERBOARD', () => {
this.showLeaderboard();
});
this.createButton(width / 2, height * 0.82, 'LEADERBOARD', () => this.showLeaderboard());
this.add.text(width / 2, height * 0.92, 'Web3 integration coming soon', {
this.createMuteButton();
this.add.text(width / 2, height * 0.94, 'Web3 integration coming soon', {
fontFamily: '"Press Start 2P", monospace',
fontSize: '9px',
color: '#444',
@@ -78,6 +53,22 @@ export class MenuScene extends Scene {
}).setOrigin(0.5);
this.createAmbientParticles();
this.input.once('pointerdown', () => {
sound.init();
sound.resume();
});
this.input.keyboard.once('keydown', () => {
sound.init();
sound.resume();
});
}
startGame() {
sound.init();
sound.resume();
sound.click();
this.scene.start('GameScene');
}
createButton(x, y, text, callback) {
@@ -99,11 +90,31 @@ export class MenuScene extends Scene {
bg.setFillStyle(0x581c87);
this.tweens.add({ targets: [bg, label], scaleX: 1, scaleY: 1, duration: 100 });
});
bg.on('pointerdown', callback);
bg.on('pointerdown', () => {
sound.click();
callback();
});
return { bg, label };
}
createMuteButton() {
const { width } = this.scale;
const x = width - 30;
const y = 30;
const icon = this.add.text(x, y, sound.isMuted() ? '🔇' : '🔊', {
fontSize: '24px',
}).setOrigin(0.5).setInteractive({ useHandCursor: true });
icon.on('pointerdown', () => {
sound.init();
const next = !sound.isMuted();
sound.setMuted(next);
icon.setText(next ? '🔇' : '🔊');
if (!next) sound.click();
});
}
showLeaderboard() {
const { width, height } = this.scale;
const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.7).setDepth(100);
@@ -138,6 +149,7 @@ export class MenuScene extends Scene {
});
close.on('pointerdown', () => {
sound.click();
overlay.destroy();
panel.destroy();
title.destroy();