119 lines
4.4 KiB
JavaScript
119 lines
4.4 KiB
JavaScript
/**
|
||
* Hauptmenü mit Propaganda-Laufband. Beim allerersten Start wird zuerst der
|
||
* Disclaimer angezeigt; das Menü ist erst danach bedienbar.
|
||
* @module scenes/MenuScene
|
||
*/
|
||
|
||
import { GAME } from '../config.js';
|
||
import { MENU, PROPAGANDA, TAGLINE, TITLE } from '../content/texts.js';
|
||
import { TEXTURE_KEYS } from '../systems/Placeholders.js';
|
||
import { MenuList } from '../ui/MenuList.js';
|
||
import { textStyle, UI, uiStyle } from '../ui/theme.js';
|
||
|
||
/** Geschwindigkeit des Laufbands in px/s. */
|
||
const TICKER_SPEED = 90;
|
||
/** Anzahl Scheinwerferkegel im Hintergrund. */
|
||
const SEARCHLIGHTS = 3;
|
||
|
||
export class MenuScene extends Phaser.Scene {
|
||
constructor() {
|
||
super('Menu');
|
||
}
|
||
|
||
create() {
|
||
const { width, height } = this.scale;
|
||
const services = this.registry.get('services');
|
||
this.input.setDefaultCursor('default');
|
||
|
||
this.drawBackground(width, height);
|
||
|
||
this.add.image(width / 2, 150, TEXTURE_KEYS.emblem).setDisplaySize(150, 150);
|
||
this.add
|
||
.text(width / 2, 262, TITLE, textStyle({ fontSize: '68px', color: UI.colors.gold, strokeThickness: 10, stroke: '#7a0a12' }))
|
||
.setOrigin(0.5);
|
||
this.add.text(width / 2, 318, TAGLINE, textStyle({ fontSize: '24px' })).setOrigin(0.5);
|
||
|
||
this.menu = new MenuList(this, width / 2, 372, [
|
||
{ label: MENU.start, onSelect: () => this.startMission() },
|
||
{ label: MENU.highscores, onSelect: () => this.scene.start('Highscores') },
|
||
{ label: MENU.controls, onSelect: () => this.scene.start('Controls') },
|
||
{ label: MENU.disclaimer, onSelect: () => this.openDisclaimer(false) },
|
||
{ label: MENU.credits, onSelect: () => this.scene.start('Credits') },
|
||
], { spacing: 54 });
|
||
|
||
this.add.text(width / 2, height - 62, MENU.hint, uiStyle({ fontSize: '16px', color: UI.colors.muted })).setOrigin(0.5);
|
||
this.createTicker(width, height);
|
||
|
||
// Reine Touch-Geräte (Handy, Tablet) haben weder WASD noch Maus – lieber gleich sagen.
|
||
if (globalThis.matchMedia?.('(pointer: coarse)').matches && !globalThis.matchMedia?.('(any-pointer: fine)').matches) {
|
||
this.add.text(width / 2, height - 92, MENU.touchNotice, uiStyle({ fontSize: '18px', color: UI.colors.danger })).setOrigin(0.5);
|
||
}
|
||
|
||
// Ton darf erst nach einer Nutzeraktion starten (Browser-Richtlinie); die Musik
|
||
// wird vorgemerkt und beginnt mit dem ersten Klick oder Tastendruck.
|
||
services.audio.startMusic();
|
||
this.input.once('pointerdown', () => services.audio.unlock());
|
||
this.input.keyboard.once('keydown', () => services.audio.unlock());
|
||
|
||
if (!services.settings.isDisclaimerAccepted()) {
|
||
this.openDisclaimer(true);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {boolean} mandatory Beim ersten Start muss bestätigt werden.
|
||
*/
|
||
openDisclaimer(mandatory) {
|
||
this.menu.setEnabled(false);
|
||
this.scene.launch('Disclaimer', { mandatory });
|
||
this.scene.get('Disclaimer').events.once('disclaimer-closed', () => {
|
||
this.menu.setEnabled(true);
|
||
});
|
||
}
|
||
|
||
startMission() {
|
||
this.registry.get('services').audio.unlock();
|
||
this.scene.start('Briefing');
|
||
}
|
||
|
||
drawBackground(width, height) {
|
||
const bg = this.add.graphics();
|
||
bg.fillGradientStyle(0x0d1321, 0x0d1321, 0x2a0a10, 0x2a0a10, 1);
|
||
bg.fillRect(0, 0, width, height);
|
||
// Diagonale Streifen wie auf einem Propagandaplakat.
|
||
bg.fillStyle(0xc1121f, 0.08);
|
||
for (let x = -height; x < width; x += 90) {
|
||
bg.fillTriangle(x, height, x + 45, height, x + height + 45, 0);
|
||
}
|
||
|
||
for (let i = 0; i < SEARCHLIGHTS; i += 1) {
|
||
const beam = this.add.triangle(width * (0.2 + 0.3 * i), height + 20, 0, 0, -120, -height * 1.1, 120, -height * 1.1, 0xf2c14e, 0.06);
|
||
beam.setOrigin(0, 0);
|
||
this.tweens.add({
|
||
targets: beam,
|
||
angle: { from: -25, to: 25 },
|
||
duration: 4200 + i * 900,
|
||
yoyo: true,
|
||
repeat: -1,
|
||
ease: 'Sine.easeInOut',
|
||
});
|
||
}
|
||
}
|
||
|
||
createTicker(width, height) {
|
||
const bar = this.add.rectangle(0, height - 36, width, 36, 0xc1121f, 0.9).setOrigin(0, 0);
|
||
bar.setDepth(GAME.depth.hud);
|
||
const line = PROPAGANDA.join(' ★ ');
|
||
this.ticker = this.add
|
||
.text(width, height - 18, line, uiStyle({ fontSize: '19px', strokeThickness: 0 }))
|
||
.setOrigin(0, 0.5)
|
||
.setDepth(GAME.depth.hud + 1);
|
||
}
|
||
|
||
update(_time, delta) {
|
||
if (!this.ticker) return;
|
||
this.ticker.x -= (TICKER_SPEED * delta) / 1000;
|
||
if (this.ticker.x < -this.ticker.width) this.ticker.x = this.scale.width;
|
||
}
|
||
}
|