slop game oder so
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Senkrechte Menüliste aus Canvas-Texten, bedienbar mit Maus und Tastatur.
|
||||
* @module ui/MenuList
|
||||
*/
|
||||
|
||||
import { oncePerEvent } from '../systems/keyboard.js';
|
||||
import { UI } from './theme.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} MenuItem
|
||||
* @property {string} label
|
||||
* @property {() => void} onSelect
|
||||
*/
|
||||
|
||||
export class MenuList {
|
||||
/**
|
||||
* @param {Phaser.Scene} scene
|
||||
* @param {number} x Mittelpunkt
|
||||
* @param {number} y Oberkante des ersten Eintrags
|
||||
* @param {MenuItem[]} items
|
||||
* @param {{spacing?: number, fontSize?: number}} [options]
|
||||
*/
|
||||
constructor(scene, x, y, items, options = {}) {
|
||||
this.scene = scene;
|
||||
this.items = items;
|
||||
this.audio = scene.registry.get('services')?.audio ?? null;
|
||||
this.index = 0;
|
||||
this.enabled = true;
|
||||
const spacing = options.spacing ?? 58;
|
||||
const fontSize = options.fontSize ?? 30;
|
||||
|
||||
this.texts = items.map((item, i) => {
|
||||
const text = scene.add
|
||||
.text(x, y + i * spacing, item.label.toUpperCase(), {
|
||||
...UI.text,
|
||||
fontSize: `${fontSize}px`,
|
||||
color: UI.colors.cream,
|
||||
})
|
||||
.setOrigin(0.5)
|
||||
.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerover', () => {
|
||||
if (this.enabled) this.focus(i);
|
||||
});
|
||||
text.on('pointerup', () => {
|
||||
if (this.enabled) this.select(i);
|
||||
});
|
||||
return text;
|
||||
});
|
||||
|
||||
this.keys = scene.input.keyboard.addKeys('UP,DOWN,W,S,ENTER,SPACE');
|
||||
this.onKeyDown = oncePerEvent((event) => {
|
||||
if (!this.enabled || event.repeat) return;
|
||||
if (event.code === 'ArrowUp' || event.code === 'KeyW') this.focus(this.index - 1);
|
||||
else if (event.code === 'ArrowDown' || event.code === 'KeyS') this.focus(this.index + 1);
|
||||
else if (event.code === 'Enter' || event.code === 'Space' || event.code === 'NumpadEnter') this.select(this.index);
|
||||
});
|
||||
scene.input.keyboard.on('keydown', this.onKeyDown);
|
||||
scene.events.once('shutdown', () => this.destroy());
|
||||
this.focus(0);
|
||||
}
|
||||
|
||||
/** @param {number} index */
|
||||
focus(index) {
|
||||
const count = this.items.length;
|
||||
const next = (index + count) % count;
|
||||
if (next !== this.index) this.audio?.play('ui-click');
|
||||
this.index = next;
|
||||
this.texts.forEach((text, i) => {
|
||||
const active = i === this.index;
|
||||
text.setColor(active ? UI.colors.gold : UI.colors.cream);
|
||||
text.setScale(active ? 1.08 : 1);
|
||||
// Großbuchstaben wie auf einem Plakat; die Texte selbst bleiben normal geschrieben.
|
||||
const label = this.items[i].label.toUpperCase();
|
||||
text.setText(active ? `» ${label} «` : label);
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {number} index */
|
||||
select(index) {
|
||||
this.focus(index);
|
||||
this.audio?.play('ui-confirm');
|
||||
this.items[index].onSelect();
|
||||
}
|
||||
|
||||
/** @param {boolean} enabled */
|
||||
setEnabled(enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.scene.input.keyboard?.off('keydown', this.onKeyDown);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Gemeinsame Text- und Farbstile für Canvas-Texte (Menüs, HUD).
|
||||
*
|
||||
* Es werden bewusst nur Systemschriften verwendet: Nichts wird mitgeliefert, also
|
||||
* gibt es keine Lizenzfragen, und die Buchstaben sind eindeutig lesbar (die
|
||||
* Tastenhinweise „Q“, „X“ oder „K“ dürfen nicht wie „O“ oder „H“ aussehen).
|
||||
* @module ui/theme
|
||||
*/
|
||||
|
||||
export const UI = Object.freeze({
|
||||
/** Plakatschrift für Titel und große Zahlen. */
|
||||
titleFont: 'Impact, Haettenschweiler, "Arial Narrow Bold", "Arial Black", sans-serif',
|
||||
/** Gut lesbare Schrift für HUD, Tabellen und Fließtext. */
|
||||
uiFont: '"Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
/** Für Pfeile und Symbole. */
|
||||
symbolFont: '"Segoe UI Symbol", "Segoe UI", system-ui, sans-serif',
|
||||
colors: Object.freeze({
|
||||
gold: '#f2c14e',
|
||||
cream: '#f4ecd8',
|
||||
crimson: '#c1121f',
|
||||
muted: '#a9b1c4',
|
||||
navy: '#0d1321',
|
||||
danger: '#ff6b5a',
|
||||
ok: '#5ad469',
|
||||
cyan: '#9be7ff',
|
||||
}),
|
||||
/** Standard-Textstil (Titelschrift) mit dunkler Kontur für Lesbarkeit auf jedem Hintergrund. */
|
||||
text: Object.freeze({
|
||||
fontFamily: 'Impact, Haettenschweiler, "Arial Narrow Bold", "Arial Black", sans-serif',
|
||||
fontSize: '20px',
|
||||
color: '#f4ecd8',
|
||||
stroke: '#0d1321',
|
||||
strokeThickness: 4,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Titelschrift-Stil mit Änderungen.
|
||||
* @param {Partial<Phaser.Types.GameObjects.Text.TextStyle>} overrides
|
||||
* @returns {Phaser.Types.GameObjects.Text.TextStyle}
|
||||
*/
|
||||
export function textStyle(overrides = {}) {
|
||||
return { ...UI.text, ...overrides };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie {@link textStyle}, aber in der gut lesbaren, fetten UI-Schrift (HUD, Tabellen, Fließtext).
|
||||
* @param {Partial<Phaser.Types.GameObjects.Text.TextStyle>} overrides
|
||||
* @returns {Phaser.Types.GameObjects.Text.TextStyle}
|
||||
*/
|
||||
export function uiStyle(overrides = {}) {
|
||||
return { ...UI.text, fontFamily: UI.uiFont, fontStyle: 'bold', ...overrides };
|
||||
}
|
||||
Reference in New Issue
Block a user