slop game oder so

This commit is contained in:
2026-09-24 17:57:54 +02:00
commit 4c997eee21
166 changed files with 14976 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
/**
* 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;
}
}