managarten/games/whopixels/js/scenes/MainMenuScene.js
Till JS c0c11c325a feat(whopixels): major refactor with 20 improvements across architecture, gameplay, UX, security, and i18n
Split monolithic RPGScene.js (1210 lines) into modular manager classes:
- WorldManager, PlayerManager, NPCManager, ChatUI, StorageManager,
  SoundManager, TouchControls

Key improvements:
- Constants config (GAME_CONFIG) replacing all magic numbers
- JSDoc types + jsconfig.json for IDE type-safety
- LocalStorage persistence for progress, stats, and custom avatars
- Synthesized sound effects via Web Audio API
- 26 NPCs (up from 10) in 3 categories
- Stats/leaderboard in main menu
- Pixel editor avatar integration with RPG game
- Mobile touch controls (virtual joystick + interact button)
- Chat UI with typing indicator and conversation history
- Interactive tutorial overlay for first-time players
- Floating question mark over NPCs in range
- Server hardened: rate limiting, input sanitization, CORS restrictions,
  API timeouts, conversation history cap
- Particle effect object pooling
- i18n framework with DE/EN and language switcher

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:26:40 +01:00

130 lines
3.1 KiB
JavaScript

class MainMenuScene extends Phaser.Scene {
constructor() {
super({ key: 'MainMenuScene' });
}
create() {
const { COLORS } = GAME_CONFIG;
const centerX = this.cameras.main.width / 2;
this.add.image(centerX, 300, 'background');
// Titel
this.add
.text(centerX, 100, I18N.t('title'), {
fontSize: '64px',
fill: COLORS.TEXT_WHITE,
fontStyle: 'bold',
})
.setOrigin(0.5);
this.add
.text(centerX, 160, I18N.t('subtitle'), {
fontSize: '32px',
fill: COLORS.TEXT_WHITE,
})
.setOrigin(0.5);
// Statistiken
this._showStats(centerX);
// Buttons
this._createButton(centerX, 300, I18N.t('startGame'), () => {
this.scene.start('RPGScene');
});
this._createButton(centerX, 370, I18N.t('pixelEditor'), () => {
this.scene.start('GameScene');
});
this._createButton(centerX, 440, I18N.t('resetProgress'), () => {
new StorageManager().reset();
this._showStats(centerX);
const confirmText = this.add
.text(centerX, 500, I18N.t('progressReset'), {
fontSize: '18px',
fill: COLORS.TEXT_REVEALED,
fontFamily: 'Arial',
})
.setOrigin(0.5);
this.tweens.add({
targets: confirmText,
alpha: 0,
duration: 1500,
delay: 1500,
onComplete: () => confirmText.destroy(),
});
});
// Sprach-Umschalter (rechts oben)
const langBtn = this.add
.text(this.cameras.main.width - 20, 20, I18N.getLanguage().toUpperCase(), {
fontSize: '20px',
fontFamily: 'Arial',
fontStyle: 'bold',
fill: COLORS.TEXT_WHITE,
backgroundColor: COLORS.BACK_BUTTON_BG,
padding: { x: 10, y: 5 },
})
.setOrigin(1, 0)
.setInteractive({ useHandCursor: true });
langBtn.on('pointerover', () => langBtn.setStyle({ fill: COLORS.BACK_BUTTON_HOVER }));
langBtn.on('pointerout', () => langBtn.setStyle({ fill: COLORS.TEXT_WHITE }));
langBtn.on('pointerdown', () => {
const newLang = I18N.toggle();
// Scene neu laden, um Texte zu aktualisieren
this.scene.restart();
});
}
_showStats(centerX) {
const { COLORS } = GAME_CONFIG;
const storage = new StorageManager();
const stats = storage.getStats();
if (this.statsText) this.statsText.destroy();
if (stats.totalRevealed > 0) {
this.statsText = this.add
.text(
centerX,
220,
[
`${I18N.t('statsRevealed')}: ${stats.totalRevealed}`,
`${I18N.t('statsAvgGuesses')}: ${stats.averageGuesses}`,
`${I18N.t('statsBestStreak')}: ${stats.bestStreak}`,
].join(' | '),
{
fontSize: '16px',
fontFamily: 'Arial',
fill: COLORS.TEXT_NPC_RESPONSE,
align: 'center',
}
)
.setOrigin(0.5);
}
}
_createButton(x, y, label, onClick) {
const { COLORS } = GAME_CONFIG;
const button = this.add
.text(x, y, label, {
fontSize: '28px',
fill: COLORS.TEXT_WHITE,
backgroundColor: COLORS.BACK_BUTTON_BG,
padding: { x: 20, y: 10 },
})
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
button.on('pointerover', () => button.setStyle({ fill: COLORS.BACK_BUTTON_HOVER }));
button.on('pointerout', () => button.setStyle({ fill: COLORS.TEXT_WHITE }));
button.on('pointerdown', onClick);
return button;
}
}