Sprint 3: architecture — UI utility, achievements menu
- Extract createButton/pixelText helpers to src/utils/ui.js with sensible defaults and per-call option overrides - MenuScene now shows BADGES button opening the achievement panel (10 entries, count of unlocked, star icons for completed) - GameOverScene buttons migrated to shared utility, removing duplicate hover/click handlers - Smaller LEADERBOARD button to make room for BADGES alongside
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Scene } from 'phaser';
|
||||
import { sound } from '../managers/SoundManager.js';
|
||||
import { createButton } from '../utils/ui.js';
|
||||
|
||||
export class GameOverScene extends Scene {
|
||||
constructor() {
|
||||
@@ -48,15 +49,8 @@ export class GameOverScene extends Scene {
|
||||
color: '#a855f7',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.createButton(width / 2, height * 0.72, 'RETRY', () => {
|
||||
sound.click();
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
|
||||
this.createButton(width / 2, height * 0.82, 'MAIN MENU', () => {
|
||||
sound.click();
|
||||
this.scene.start('MenuScene');
|
||||
});
|
||||
createButton(this, width / 2, height * 0.72, 'RETRY', () => this.scene.start('GameScene'));
|
||||
createButton(this, width / 2, height * 0.82, 'MAIN MENU', () => this.scene.start('MenuScene'));
|
||||
|
||||
this.add.text(width / 2, height * 0.92, 'ON-CHAIN SUBMIT — COMING SOON', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
@@ -74,19 +68,4 @@ export class GameOverScene extends Scene {
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
}
|
||||
|
||||
createButton(x, y, text, callback) {
|
||||
const bg = this.add.rectangle(x, y, 260, 48, 0x581c87)
|
||||
.setStrokeStyle(2, 0xa855f7)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
const label = this.add.text(x, y, text, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '13px',
|
||||
color: '#ffffff',
|
||||
}).setOrigin(0.5);
|
||||
bg.on('pointerover', () => bg.setFillStyle(0x7e22ce));
|
||||
bg.on('pointerout', () => bg.setFillStyle(0x581c87));
|
||||
bg.on('pointerdown', callback);
|
||||
return { bg, label };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Scene } from 'phaser';
|
||||
import { sound } from '../managers/SoundManager.js';
|
||||
import { AchievementsManager } from '../managers/AchievementsManager.js';
|
||||
import { createButton } from '../utils/ui.js';
|
||||
|
||||
export class MenuScene extends Scene {
|
||||
constructor() {
|
||||
@@ -11,27 +13,29 @@ export class MenuScene extends Scene {
|
||||
|
||||
this.add.tileSprite(width / 2, height / 2, width, height, 'gridBg');
|
||||
|
||||
this.add.text(width / 2, height * 0.18, 'NADDIE JUMP', {
|
||||
this.add.text(width / 2, height * 0.16, 'NADDIE JUMP', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '38px',
|
||||
color: '#d8b4fe',
|
||||
align: 'center',
|
||||
}).setOrigin(0.5).setShadow(4, 4, '#581c87', 0, false, true);
|
||||
|
||||
this.add.text(width / 2, height * 0.27, 'MONAD EDITION', {
|
||||
this.add.text(width / 2, height * 0.25, 'MONAD EDITION', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '14px',
|
||||
color: '#a855f7',
|
||||
align: 'center',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
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' });
|
||||
const preview = this.add.image(width / 2, height * 0.44, 'player_idle').setScale(0.55);
|
||||
this.tweens.add({ targets: preview, y: height * 0.44 - 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' });
|
||||
|
||||
this.createButton(width / 2, height * 0.68, 'START GAME', () => this.startGame());
|
||||
createButton(this, width / 2, height * 0.62, 'START GAME', () => this.startGame(), {
|
||||
width: 280, height: 56, fontSize: '15px',
|
||||
});
|
||||
|
||||
this.add.text(width / 2, height * 0.74, 'ENTER / SPACE / TAP', {
|
||||
this.add.text(width / 2, height * 0.68, 'ENTER / SPACE / TAP', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '10px',
|
||||
color: '#888',
|
||||
@@ -41,7 +45,12 @@ export class MenuScene extends Scene {
|
||||
this.input.keyboard.on('keydown-ENTER', () => this.startGame());
|
||||
this.input.keyboard.on('keydown-SPACE', () => this.startGame());
|
||||
|
||||
this.createButton(width / 2, height * 0.82, 'LEADERBOARD', () => this.showLeaderboard());
|
||||
createButton(this, width / 2 - 75, height * 0.78, 'BOARD', () => this.showLeaderboard(), {
|
||||
width: 140, height: 44, fontSize: '11px',
|
||||
});
|
||||
createButton(this, width / 2 + 75, height * 0.78, 'BADGES', () => this.showAchievements(), {
|
||||
width: 140, height: 44, fontSize: '11px',
|
||||
});
|
||||
|
||||
this.createMuteButton();
|
||||
|
||||
@@ -54,14 +63,8 @@ export class MenuScene extends Scene {
|
||||
|
||||
this.createAmbientParticles();
|
||||
|
||||
this.input.once('pointerdown', () => {
|
||||
sound.init();
|
||||
sound.resume();
|
||||
});
|
||||
this.input.keyboard.once('keydown', () => {
|
||||
sound.init();
|
||||
sound.resume();
|
||||
});
|
||||
this.input.once('pointerdown', () => { sound.init(); sound.resume(); });
|
||||
this.input.keyboard.once('keydown', () => { sound.init(); sound.resume(); });
|
||||
}
|
||||
|
||||
startGame() {
|
||||
@@ -71,38 +74,9 @@ export class MenuScene extends Scene {
|
||||
this.scene.start('GameScene');
|
||||
}
|
||||
|
||||
createButton(x, y, text, callback) {
|
||||
const bg = this.add.rectangle(x, y, 280, 56, 0x581c87)
|
||||
.setStrokeStyle(3, 0xa855f7)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
|
||||
const label = this.add.text(x, y, text, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '15px',
|
||||
color: '#ffffff',
|
||||
}).setOrigin(0.5);
|
||||
|
||||
bg.on('pointerover', () => {
|
||||
bg.setFillStyle(0x7e22ce);
|
||||
this.tweens.add({ targets: [bg, label], scaleX: 1.05, scaleY: 1.05, duration: 100 });
|
||||
});
|
||||
bg.on('pointerout', () => {
|
||||
bg.setFillStyle(0x581c87);
|
||||
this.tweens.add({ targets: [bg, label], scaleX: 1, scaleY: 1, duration: 100 });
|
||||
});
|
||||
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() ? '🔇' : '🔊', {
|
||||
const icon = this.add.text(width - 30, 30, sound.isMuted() ? '🔇' : '🔊', {
|
||||
fontSize: '24px',
|
||||
}).setOrigin(0.5).setInteractive({ useHandCursor: true });
|
||||
|
||||
@@ -150,14 +124,64 @@ export class MenuScene extends Scene {
|
||||
|
||||
close.on('pointerdown', () => {
|
||||
sound.click();
|
||||
overlay.destroy();
|
||||
panel.destroy();
|
||||
title.destroy();
|
||||
close.destroy();
|
||||
overlay.destroy(); panel.destroy(); title.destroy(); close.destroy();
|
||||
rows.forEach(r => { r.rank.destroy(); r.addr.destroy(); r.score.destroy(); });
|
||||
});
|
||||
}
|
||||
|
||||
showAchievements() {
|
||||
const { width, height } = this.scale;
|
||||
const all = AchievementsManager.getAll();
|
||||
const unlocked = JSON.parse(localStorage.getItem('naddie_achievements_v1') || '{}');
|
||||
|
||||
const elements = [];
|
||||
const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.75).setDepth(100);
|
||||
const panel = this.add.rectangle(width / 2, height / 2, 420, 700, 0x1a0533).setStrokeStyle(3, 0xa855f7).setDepth(101);
|
||||
|
||||
const total = all.length;
|
||||
const got = Object.keys(unlocked).length;
|
||||
const title = this.add.text(width / 2, height * 0.12, `ACHIEVEMENTS ${got}/${total}`, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '14px',
|
||||
color: '#d8b4fe',
|
||||
}).setOrigin(0.5).setDepth(102);
|
||||
elements.push(title);
|
||||
|
||||
const close = this.add.text(width / 2 + 190, height * 0.12 - 5, 'X', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '18px',
|
||||
color: '#fff',
|
||||
}).setOrigin(0.5).setDepth(102).setInteractive({ useHandCursor: true });
|
||||
elements.push(close);
|
||||
|
||||
all.forEach((def, i) => {
|
||||
const y = height * 0.18 + i * 55;
|
||||
const isGot = !!unlocked[def.id];
|
||||
const icon = this.add.text(width / 2 - 180, y, isGot ? '★' : '☆', {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '18px',
|
||||
color: isGot ? '#ffd700' : '#555',
|
||||
}).setOrigin(0.5).setDepth(102);
|
||||
const nameText = this.add.text(width / 2 - 150, y - 8, def.title, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '10px',
|
||||
color: isGot ? '#ffd700' : '#888',
|
||||
}).setOrigin(0, 0.5).setDepth(102);
|
||||
const descText = this.add.text(width / 2 - 150, y + 10, def.desc, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: '8px',
|
||||
color: '#d8b4fe',
|
||||
}).setOrigin(0, 0.5).setDepth(102);
|
||||
elements.push(icon, nameText, descText);
|
||||
});
|
||||
|
||||
close.on('pointerdown', () => {
|
||||
sound.click();
|
||||
overlay.destroy(); panel.destroy();
|
||||
elements.forEach((e) => e.destroy());
|
||||
});
|
||||
}
|
||||
|
||||
createAmbientParticles() {
|
||||
const { width, height } = this.scale;
|
||||
for (let i = 0; i < 40; i++) {
|
||||
|
||||
51
src/utils/ui.js
Normal file
51
src/utils/ui.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { sound } from '../managers/SoundManager.js';
|
||||
|
||||
const DEFAULTS = {
|
||||
width: 260,
|
||||
height: 48,
|
||||
fontSize: '13px',
|
||||
bgColor: 0x581c87,
|
||||
hoverColor: 0x7e22ce,
|
||||
strokeColor: 0xa855f7,
|
||||
strokeWidth: 2,
|
||||
textColor: '#ffffff',
|
||||
hoverScale: 1.04,
|
||||
playClick: true,
|
||||
};
|
||||
|
||||
export function createButton(scene, x, y, text, callback, options = {}) {
|
||||
const opt = { ...DEFAULTS, ...options };
|
||||
|
||||
const bg = scene.add.rectangle(x, y, opt.width, opt.height, opt.bgColor)
|
||||
.setStrokeStyle(opt.strokeWidth, opt.strokeColor)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
|
||||
const label = scene.add.text(x, y, text, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: opt.fontSize,
|
||||
color: opt.textColor,
|
||||
}).setOrigin(0.5);
|
||||
|
||||
bg.on('pointerover', () => {
|
||||
bg.setFillStyle(opt.hoverColor);
|
||||
scene.tweens.add({ targets: [bg, label], scaleX: opt.hoverScale, scaleY: opt.hoverScale, duration: 100 });
|
||||
});
|
||||
bg.on('pointerout', () => {
|
||||
bg.setFillStyle(opt.bgColor);
|
||||
scene.tweens.add({ targets: [bg, label], scaleX: 1, scaleY: 1, duration: 100 });
|
||||
});
|
||||
bg.on('pointerdown', () => {
|
||||
if (opt.playClick) sound.click();
|
||||
callback();
|
||||
});
|
||||
|
||||
return { bg, label, destroy: () => { bg.destroy(); label.destroy(); } };
|
||||
}
|
||||
|
||||
export function pixelText(scene, x, y, text, size = '14px', color = '#ffffff') {
|
||||
return scene.add.text(x, y, text, {
|
||||
fontFamily: '"Press Start 2P", monospace',
|
||||
fontSize: size,
|
||||
color,
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
Reference in New Issue
Block a user