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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user