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
+136
View File
@@ -0,0 +1,136 @@
/**
* Einfache Informationsseiten im Canvas: Ehrentafel, Steuerung, Credits.
* Alle kehren mit Esc, Enter oder Klick auf „Zurück“ ins Hauptmenü zurück.
* @module scenes/InfoScenes
*/
import { ORBITAL } from '../config.js';
import { CONTROLS, CREDITS, HIGHSCORES } from '../content/texts.js';
import { formatScore, formatSequence } from '../logic/format.js';
import { oncePerEvent } from '../systems/keyboard.js';
import { textStyle, UI, uiStyle } from '../ui/theme.js';
/**
* Gemeinsamer Rahmen: Hintergrund, Titel und Zurück-Schaltfläche.
* @param {Phaser.Scene} scene
* @param {string} title
* @param {string} backLabel
*/
function frame(scene, title, backLabel) {
const { width, height } = scene.scale;
scene.input.setDefaultCursor('default');
const bg = scene.add.graphics();
bg.fillGradientStyle(0x0d1321, 0x0d1321, 0x1b2438, 0x1b2438, 1);
bg.fillRect(0, 0, width, height);
bg.lineStyle(3, 0xf2c14e, 1);
bg.strokeRect(40, 30, width - 80, height - 60);
scene.add.text(width / 2, 70, title, textStyle({ fontSize: '38px', color: UI.colors.gold })).setOrigin(0.5);
const back = scene.add
.text(width / 2, height - 70, backLabel, textStyle({ fontSize: '22px' }))
.setOrigin(0.5)
.setInteractive({ useHandCursor: true });
back.on('pointerover', () => back.setColor(UI.colors.gold));
back.on('pointerout', () => back.setColor(UI.colors.cream));
const leave = () => scene.scene.start('Menu');
back.on('pointerup', leave);
const leaveByKey = oncePerEvent((event) => {
if (!event.repeat) leave();
});
scene.input.keyboard.on('keydown-ESC', leaveByKey);
scene.input.keyboard.on('keydown-ENTER', leaveByKey);
}
export class HighscoresScene extends Phaser.Scene {
constructor() {
super('Highscores');
}
create() {
frame(this, HIGHSCORES.title, HIGHSCORES.back);
const { width } = this.scale;
this.statusText = this.add.text(width / 2, 300, HIGHSCORES.loading, uiStyle({ fontSize: '22px', color: UI.colors.muted })).setOrigin(0.5);
this.registry
.get('services')
.api.fetchHighscores()
.then((result) => {
if (!this.sys.isActive()) return;
if (!result.ok) this.statusText.setText(HIGHSCORES.error);
else if (result.highscores.length === 0) this.statusText.setText(HIGHSCORES.empty);
else {
this.statusText.destroy();
this.renderTable(result.highscores);
}
});
}
/** @param {import('../systems/ApiClient.js').HighscoreEntry[]} entries */
renderTable(entries) {
const columns = { rank: 280, name: 360, score: 900, wave: 1000 };
const top = 130;
const rowHeight = 40;
const header = uiStyle({ fontSize: '17px', color: UI.colors.muted });
this.add.text(columns.rank, top, HIGHSCORES.columns.rank, header);
this.add.text(columns.name, top, HIGHSCORES.columns.name, header);
this.add.text(columns.score, top, HIGHSCORES.columns.score, header).setOrigin(1, 0);
this.add.text(columns.wave, top, HIGHSCORES.columns.wave, header).setOrigin(1, 0);
entries.forEach((entry, i) => {
const y = top + 40 + i * rowHeight;
const color = i === 0 ? UI.colors.gold : UI.colors.cream;
const style = uiStyle({ fontSize: '24px', color });
this.add.text(columns.rank, y, `${entry.rank}.`, style);
// Canvas-Text interpretiert kein HTML – Namen werden hier sicher als Text gezeichnet.
this.add.text(columns.name, y, entry.name, style);
this.add.text(columns.score, y, formatScore(entry.score), style).setOrigin(1, 0);
this.add.text(columns.wave, y, String(entry.wave), style).setOrigin(1, 0);
});
}
}
export class ControlsScene extends Phaser.Scene {
constructor() {
super('Controls');
}
create() {
frame(this, CONTROLS.title, CONTROLS.back);
const keyStyle = uiStyle({ fontSize: '20px', color: UI.colors.gold });
const valueStyle = uiStyle({ fontSize: '20px' });
CONTROLS.rows.forEach(([key, action], i) => {
const y = 130 + i * 38;
this.add.text(560, y, key, keyStyle).setOrigin(1, 0);
this.add.text(590, y, action, valueStyle);
});
const top = 130 + CONTROLS.rows.length * 38 + 30;
this.add.text(this.scale.width / 2, top, CONTROLS.orbitalTitle, textStyle({ fontSize: '22px', color: UI.colors.gold })).setOrigin(0.5);
ORBITAL.commands.forEach((command, i) => {
const y = top + 40 + i * 36;
this.add.text(560, y, command.name, valueStyle).setOrigin(1, 0);
this.add.text(590, y, formatSequence(command.sequence, ' '), { ...keyStyle, fontFamily: UI.symbolFont });
});
}
}
export class CreditsScene extends Phaser.Scene {
constructor() {
super('Credits');
}
create() {
frame(this, CREDITS.title, CREDITS.back);
const { width } = this.scale;
let y = 118;
for (const section of CREDITS.sections) {
const heading = this.add.text(width / 2, y, section.heading, textStyle({ fontSize: '21px', color: UI.colors.gold })).setOrigin(0.5, 0);
y += heading.height + 4;
for (const line of section.lines) {
const text = this.add
.text(width / 2, y, line, { ...uiStyle({ fontSize: '17px', align: 'center' }), wordWrap: { width: width - 200 } })
.setOrigin(0.5, 0);
// Tatsächliche Höhe verwenden: umgebrochene Zeilen sind höher als eine Zeile.
y += text.height + 2;
}
y += 12;
}
}
}