managarten/games/whopixels/js/scenes/RPGScene.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

149 lines
3.8 KiB
JavaScript

class RPGScene extends Phaser.Scene {
constructor() {
super({ key: 'RPGScene' });
}
preload() {
// Wir verwenden die in BootScene erstellten Texturen
}
create() {
// Manager erstellen
this.storage = new StorageManager();
this.sound_mgr = new SoundManager();
this.worldManager = new WorldManager(this);
this.playerManager = new PlayerManager(this);
this.npcManager = new NPCManager(this);
this.chatUI = new ChatUI(this);
// Gespeicherten Fortschritt laden
const saveData = this.storage.load();
if (saveData.discoveredNPCs.length > 0) {
console.log(I18N.t('saveLoaded', { count: String(saveData.discoveredNPCs.length) }));
}
// Spielwelt erstellen
this.worldManager.create();
// Spieler erstellen
this.playerManager.create(this.worldManager.map, this.worldManager.obstacles);
// NPCs erstellen (mit gespeichertem Fortschritt)
this.npcManager.create(this.playerManager.player, this.worldManager.obstacles);
this.npcManager.state.discoveredNPCs = saveData.discoveredNPCs;
// Touch-Controls (Mobile)
this.touchControls = new TouchControls(this);
this.touchControls.create();
// UI erstellen
this._createSceneUI();
this.chatUI.create();
// Interaktions-Taste (E)
this.interactKey = this.input.keyboard.addKey('E');
// NPC-Bewegung
this.time.addEvent({
delay: GAME_CONFIG.NPC_MOVE_INTERVAL,
callback: () => this.npcManager.moveRandomly(),
callbackScope: this,
loop: true,
});
// Tutorial beim ersten Spielstart
if (saveData.totalRevealed === 0) {
this._showTutorial();
}
}
_showTutorial() {
const { COLORS } = GAME_CONFIG;
const w = this.cameras.main.width;
const h = this.cameras.main.height;
const overlay = this.add.graphics();
overlay.fillStyle(0x000000, 0.7);
overlay.fillRect(0, 0, w, h);
overlay.setScrollFactor(0);
overlay.setDepth(2000);
const isMobile = this.sys.game.device.input.touch;
const controlsText = isMobile
? I18N.t('tutorialControlsMobile')
: I18N.t('tutorialControlsDesktop');
const tutorialText = this.add.text(
w / 2,
h / 2 - 40,
[
I18N.t('tutorialWelcome'),
'',
I18N.t('tutorialDesc'),
'',
controlsText,
'',
I18N.t('tutorialStart'),
].join('\n'),
{
fontSize: '18px',
fontFamily: 'Arial',
fill: COLORS.TEXT_WHITE,
align: 'center',
lineSpacing: 6,
}
);
tutorialText.setOrigin(0.5);
tutorialText.setScrollFactor(0);
tutorialText.setDepth(2001);
const closeTutorial = () => {
overlay.destroy();
tutorialText.destroy();
};
this.input.once('pointerdown', closeTutorial);
this.input.keyboard.once('keydown', closeTutorial);
}
_createSceneUI() {
const { COLORS, FONTS } = GAME_CONFIG;
const backButton = this.add
.text(10, 10, I18N.t('backToMenu'), {
fontSize: FONTS.BACK_BUTTON,
fill: COLORS.TEXT_WHITE,
backgroundColor: COLORS.BACK_BUTTON_BG,
padding: { x: 10, y: 5 },
})
.setScrollFactor(0)
.setInteractive();
backButton.on('pointerover', () => backButton.setStyle({ fill: COLORS.BACK_BUTTON_HOVER }));
backButton.on('pointerout', () => backButton.setStyle({ fill: COLORS.TEXT_WHITE }));
backButton.on('pointerdown', () => this.scene.start('MainMenuScene'));
this.add
.text(10, 50, I18N.t('arrowKeysToMove'), {
fontSize: FONTS.INSTRUCTIONS,
fill: COLORS.TEXT_WHITE,
backgroundColor: 'rgba(0,0,0,0.5)',
padding: { x: 5, y: 2 },
})
.setScrollFactor(0);
}
update() {
this.playerManager.handleMovement(this.touchControls);
const touchInteract = this.touchControls.isActive && this.touchControls.consumeInteract();
if (this.npcManager.checkInteraction(this.interactKey, touchInteract)) {
this.chatUI.lastNpcResponse = I18N.t('riddleIntro');
this.chatUI.conversationHistory = [];
this.chatUI.open();
this.sound_mgr.playConversationStart();
}
this.npcManager.update();
}
}