/** * Einstiegspunkt des Frontends: liest die Server-URLs aus dem HTML, baut die * gemeinsamen Dienste (Einstellungen, Audio, API) und startet Phaser. * @module main */ import { GAME } from './config.js'; import { ERRORS } from './content/texts.js'; import { BootScene } from './scenes/BootScene.js'; import { BriefingScene } from './scenes/BriefingScene.js'; import { DisclaimerScene } from './scenes/DisclaimerScene.js'; import { GameOverScene } from './scenes/GameOverScene.js'; import { GameScene } from './scenes/GameScene.js'; import { HudScene } from './scenes/HudScene.js'; import { ControlsScene, CreditsScene, HighscoresScene } from './scenes/InfoScenes.js'; import { MenuScene } from './scenes/MenuScene.js'; import { PauseScene } from './scenes/PauseScene.js'; import { ApiClient } from './systems/ApiClient.js'; import { AudioManager } from './systems/AudioManager.js'; import { SafeStorage, Settings } from './systems/Settings.js'; /** @param {string} message */ function showBootError(message) { const target = document.getElementById('boot-message'); if (target) { target.textContent = message; target.classList.add('boot-message--error'); } } /** * Liest die vom Server gerenderten URLs (funktioniert auch unter einer Unter-URL). * @param {HTMLElement} shell */ function readContext(shell) { return { staticBase: shell.dataset.staticBase ?? '/static/', api: { highscores: shell.dataset.apiHighscores ?? '/api/highscores', runs: shell.dataset.apiRuns ?? '/api/runs', }, disclaimerUrl: shell.dataset.disclaimerUrl ?? '/disclaimer', }; } function start() { const shell = document.getElementById('game-shell'); if (!shell) return; if (typeof window.Phaser === 'undefined') { showBootError(ERRORS.phaserMissing); return; } const context = readContext(shell); const settings = new Settings(SafeStorage.forBrowser()); const services = { settings, audio: new AudioManager(settings, { staticBase: context.staticBase }), api: new ApiClient(context.api), }; // Klangdateien schon jetzt als Bytes laden; dekodiert wird nach der ersten Interaktion. services.audio.preload(); const game = new Phaser.Game({ type: Phaser.AUTO, parent: 'game', width: GAME.width, height: GAME.height, backgroundColor: GAME.backgroundColor, banner: false, // Rechtsklick soll kein Browser-Kontextmenü über dem Spiel öffnen. disableContextMenu: true, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, physics: { default: 'arcade', arcade: { debug: false } }, // Ton übernimmt der eigene AudioManager (Kanäle, Synthesizer, Musik). Ohne diese // Einstellung würde Phaser zusätzlich einen eigenen AudioContext anlegen. audio: { noAudio: true }, // Reihenfolge = Zeichenreihenfolge: HUD und Pause liegen über dem Spiel. scene: [ BootScene, MenuScene, DisclaimerScene, BriefingScene, GameScene, HudScene, PauseScene, GameOverScene, HighscoresScene, ControlsScene, CreditsScene, ], callbacks: { preBoot: (g) => { g.registry.set('context', context); g.registry.set('services', services); }, }, }); // Für End-to-End-Tests und Fehlersuche in der Browser-Konsole. window.gloryDefense = game; } try { start(); } catch (error) { console.error(error); showBootError(ERRORS.generic); }