mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
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>
165 lines
4.7 KiB
JavaScript
165 lines
4.7 KiB
JavaScript
/**
|
|
* Touch-Controls für Mobile-Unterstützung.
|
|
* Zeigt einen virtuellen Joystick und einen Interaktions-Button.
|
|
*/
|
|
class TouchControls {
|
|
/** @param {Phaser.Scene} scene */
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.isActive = false;
|
|
this.direction = { x: 0, y: 0 };
|
|
this.interactPressed = false;
|
|
|
|
// Joystick-Elemente
|
|
this.joystickBase = null;
|
|
this.joystickThumb = null;
|
|
this.interactButton = null;
|
|
|
|
// Joystick-State
|
|
this.joystickPointer = null;
|
|
this.joystickCenter = { x: 0, y: 0 };
|
|
}
|
|
|
|
create() {
|
|
// Nur auf Touch-Geräten aktivieren
|
|
if (!this.scene.sys.game.device.input.touch) return;
|
|
|
|
this.isActive = true;
|
|
const width = this.scene.cameras.main.width;
|
|
const height = this.scene.cameras.main.height;
|
|
|
|
// Joystick-Base (links unten)
|
|
const joyX = 100;
|
|
const joyY = height - 100;
|
|
this.joystickCenter = { x: joyX, y: joyY };
|
|
|
|
this.joystickBase = this.scene.add.graphics();
|
|
this.joystickBase.fillStyle(0xffffff, 0.2);
|
|
this.joystickBase.fillCircle(joyX, joyY, 60);
|
|
this.joystickBase.lineStyle(2, 0xffffff, 0.4);
|
|
this.joystickBase.strokeCircle(joyX, joyY, 60);
|
|
this.joystickBase.setScrollFactor(0);
|
|
this.joystickBase.setDepth(1000);
|
|
|
|
this.joystickThumb = this.scene.add.graphics();
|
|
this._drawThumb(joyX, joyY);
|
|
this.joystickThumb.setScrollFactor(0);
|
|
this.joystickThumb.setDepth(1001);
|
|
|
|
// Interaktions-Button (rechts unten)
|
|
const btnX = width - 80;
|
|
const btnY = height - 100;
|
|
|
|
this.interactButton = this.scene.add.graphics();
|
|
this.interactButton.fillStyle(GAME_CONFIG.COLORS.CHAT_BORDER, 0.6);
|
|
this.interactButton.fillCircle(btnX, btnY, 35);
|
|
this.interactButton.setScrollFactor(0);
|
|
this.interactButton.setDepth(1000);
|
|
|
|
const btnLabel = this.scene.add.text(btnX, btnY, 'E', {
|
|
fontSize: '28px',
|
|
fontFamily: 'Arial',
|
|
fontStyle: 'bold',
|
|
fill: GAME_CONFIG.COLORS.TEXT_WHITE,
|
|
});
|
|
btnLabel.setOrigin(0.5);
|
|
btnLabel.setScrollFactor(0);
|
|
btnLabel.setDepth(1001);
|
|
|
|
// Interaktiver Bereich für den Button
|
|
const btnHit = this.scene.add.circle(btnX, btnY, 40);
|
|
btnHit.setScrollFactor(0);
|
|
btnHit.setInteractive();
|
|
btnHit.setAlpha(0.001);
|
|
btnHit.setDepth(1002);
|
|
|
|
btnHit.on('pointerdown', () => {
|
|
this.interactPressed = true;
|
|
this.interactButton.clear();
|
|
this.interactButton.fillStyle(GAME_CONFIG.COLORS.SEND_BUTTON_HOVER, 0.8);
|
|
this.interactButton.fillCircle(btnX, btnY, 35);
|
|
});
|
|
|
|
btnHit.on('pointerup', () => {
|
|
this.interactButton.clear();
|
|
this.interactButton.fillStyle(GAME_CONFIG.COLORS.CHAT_BORDER, 0.6);
|
|
this.interactButton.fillCircle(btnX, btnY, 35);
|
|
});
|
|
|
|
// Joystick Touch-Handling
|
|
this.scene.input.on('pointerdown', (pointer) => this._onPointerDown(pointer));
|
|
this.scene.input.on('pointermove', (pointer) => this._onPointerMove(pointer));
|
|
this.scene.input.on('pointerup', (pointer) => this._onPointerUp(pointer));
|
|
}
|
|
|
|
/** @param {number} x @param {number} y */
|
|
_drawThumb(x, y) {
|
|
this.joystickThumb.clear();
|
|
this.joystickThumb.fillStyle(0xffffff, 0.5);
|
|
this.joystickThumb.fillCircle(x, y, 25);
|
|
}
|
|
|
|
/** @param {Phaser.Input.Pointer} pointer */
|
|
_onPointerDown(pointer) {
|
|
if (!this.isActive) return;
|
|
|
|
// Nur linke Hälfte des Bildschirms für Joystick
|
|
if (pointer.x < this.scene.cameras.main.width / 2) {
|
|
const dist = Phaser.Math.Distance.Between(
|
|
pointer.x,
|
|
pointer.y,
|
|
this.joystickCenter.x,
|
|
this.joystickCenter.y
|
|
);
|
|
if (dist < 80) {
|
|
this.joystickPointer = pointer;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @param {Phaser.Input.Pointer} pointer */
|
|
_onPointerMove(pointer) {
|
|
if (!this.isActive || !this.joystickPointer || pointer.id !== this.joystickPointer.id) return;
|
|
|
|
const maxDist = 50;
|
|
const dx = pointer.x - this.joystickCenter.x;
|
|
const dy = pointer.y - this.joystickCenter.y;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
let thumbX, thumbY;
|
|
if (dist > maxDist) {
|
|
thumbX = this.joystickCenter.x + (dx / dist) * maxDist;
|
|
thumbY = this.joystickCenter.y + (dy / dist) * maxDist;
|
|
} else {
|
|
thumbX = pointer.x;
|
|
thumbY = pointer.y;
|
|
}
|
|
|
|
this._drawThumb(thumbX, thumbY);
|
|
|
|
// Richtung normalisieren
|
|
const normDist = Math.min(dist, maxDist) / maxDist;
|
|
this.direction.x = (dx / (dist || 1)) * normDist;
|
|
this.direction.y = (dy / (dist || 1)) * normDist;
|
|
}
|
|
|
|
/** @param {Phaser.Input.Pointer} pointer */
|
|
_onPointerUp(pointer) {
|
|
if (!this.isActive) return;
|
|
|
|
if (this.joystickPointer && pointer.id === this.joystickPointer.id) {
|
|
this.joystickPointer = null;
|
|
this.direction = { x: 0, y: 0 };
|
|
this._drawThumb(this.joystickCenter.x, this.joystickCenter.y);
|
|
}
|
|
}
|
|
|
|
/** @returns {boolean} Ob der Interact-Button gedrückt wurde (einmalig) */
|
|
consumeInteract() {
|
|
if (this.interactPressed) {
|
|
this.interactPressed = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|