Files
2026-09-24 17:57:54 +02:00

213 lines
6.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Erzeugt alle Platzhalter-Texturen zur Laufzeit aus einfachen Formen.
*
* Damit ist das Spiel ohne eine einzige Bilddatei vollständig spielbar. Echte
* Grafiken (Phase „Assets“) werden unter denselben Schlüsseln geladen; fehlt eine
* Datei oder schlägt das Laden fehl, springt hier der Platzhalter ein.
*
* Konvention: Alle Figuren „schauen“ nach rechts (Winkel 0), damit
* `setRotation(zielwinkel)` ohne Versatz funktioniert.
* @module systems/Placeholders
*/
import { COLORS, ENEMY_TYPES, PLAYER } from '../config.js';
/** Alle Texturschlüssel, die das Spiel verwendet. */
export const TEXTURE_KEYS = Object.freeze({
player: 'player',
enemy: (typeId) => `enemy-${typeId}`,
bulletPlayer: 'bullet-player',
bulletEnemy: 'bullet-enemy',
bulletTurret: 'bullet-turret',
turret: 'turret',
supplyPod: 'supply-pod',
ground: 'ground',
particle: 'particle',
crosshair: 'crosshair',
emblem: 'emblem',
});
/**
* Zeichnet mit einer temporären Graphics-Instanz und speichert das Ergebnis als Textur.
* Bereits vorhandene Texturen (z. B. geladene Grafiken) werden nicht überschrieben.
* @param {Phaser.Scene} scene
* @param {string} key
* @param {number} width
* @param {number} height
* @param {(g: Phaser.GameObjects.Graphics) => void} draw
*/
function makeTexture(scene, key, width, height, draw) {
if (scene.textures.exists(key)) return;
const g = scene.make.graphics({ x: 0, y: 0 }, false);
draw(g);
g.generateTexture(key, width, height);
g.destroy();
}
function drawPlayer(g) {
const s = PLAYER.radius * 2 + 12;
const c = s / 2;
// Umhang (hinten, also links) in den Farben der Vereinigten Erde.
g.fillStyle(COLORS.playerCape, 1);
g.fillTriangle(c - 4, c - 16, c - 4, c + 16, 2, c);
g.fillStyle(COLORS.gold, 1);
g.fillRect(c - 8, c - 2, 6, 4);
// Rüstung
g.fillStyle(COLORS.player, 1);
g.fillCircle(c, c, PLAYER.radius - 2);
g.lineStyle(3, COLORS.gold, 1);
g.strokeCircle(c, c, PLAYER.radius - 2);
// Geschlossener Helm mit Visier – bewusst ohne Gesicht.
g.fillStyle(COLORS.navy, 1);
g.fillRoundedRect(c + 2, c - 7, 9, 14, 3);
// Waffe
g.fillStyle(COLORS.navy, 1);
g.fillRect(c + 6, c + 4, s / 2 - 6, 5);
}
function drawSwarm(g, size, variant) {
const c = size / 2;
const r = size / 2 - 3;
g.lineStyle(2, COLORS.swarmDark, 1);
// Beine
for (let i = -1; i <= 1; i += 1) {
g.lineBetween(c + i * r * 0.4, c - r * 0.5, c + i * r * 0.55, 1);
g.lineBetween(c + i * r * 0.4, c + r * 0.5, c + i * r * 0.55, size - 1);
}
g.fillStyle(COLORS.swarm, 1);
g.fillEllipse(c - r * 0.15, c, r * 1.6, r * 1.2);
g.fillStyle(COLORS.swarmDark, 1);
g.fillCircle(c + r * 0.6, c, r * 0.35);
if (variant === 'medium') {
// Sprungkralle: kräftige Klauen vorne
g.lineStyle(3, COLORS.swarmDark, 1);
g.lineBetween(c + r * 0.6, c - r * 0.3, size - 1, c - r * 0.6);
g.lineBetween(c + r * 0.6, c + r * 0.3, size - 1, c + r * 0.6);
}
if (variant === 'elite') {
// Brutmutter: Eiersack
g.fillStyle(0xd9c27a, 1);
for (let i = 0; i < 5; i += 1) g.fillCircle(c - r * 0.6 + (i % 2) * 6, c - 10 + i * 5, 5);
}
}
function drawLegion(g, size, variant) {
const c = size / 2;
const r = size / 2 - 3;
g.fillStyle(COLORS.legionDark, 1);
if (variant === 'elite') {
// Walzmeister: Ketten links und rechts
g.fillRect(4, 2, size - 8, 8);
g.fillRect(4, size - 10, size - 8, 8);
}
g.fillStyle(COLORS.legion, 1);
if (variant === 'small') {
g.fillCircle(c, c, r);
} else {
g.fillRoundedRect(c - r, c - r * 0.8, r * 1.7, r * 1.6, 5);
}
g.lineStyle(2, COLORS.legionDark, 1);
g.strokeCircle(c, c, r * 0.55);
// Rotes „Auge“
g.fillStyle(COLORS.enemyBullet, 1);
g.fillCircle(c + r * 0.35, c, Math.max(2, r * 0.18));
// Waffenlauf(e)
g.fillStyle(COLORS.legionDark, 1);
if (variant === 'elite') {
g.fillRect(c + r * 0.5, c - 10, r * 0.55, 6);
g.fillRect(c + r * 0.5, c + 4, r * 0.55, 6);
} else {
g.fillRect(c + r * 0.5, c - 2, r * 0.55, 4);
}
}
/**
* Erzeugt alle Platzhalter, die noch nicht als echte Grafik geladen wurden.
* @param {Phaser.Scene} scene
*/
export function createPlaceholderTextures(scene) {
const playerSize = PLAYER.radius * 2 + 12;
makeTexture(scene, TEXTURE_KEYS.player, playerSize, playerSize, drawPlayer);
for (const type of Object.values(ENEMY_TYPES)) {
const size = type.radius * 2 + 6;
makeTexture(scene, TEXTURE_KEYS.enemy(type.id), size, size, (g) =>
type.faction === 'swarm' ? drawSwarm(g, size, type.tier) : drawLegion(g, size, type.tier),
);
}
makeTexture(scene, TEXTURE_KEYS.bulletPlayer, 14, 6, (g) => {
g.fillStyle(COLORS.playerBullet, 1);
g.fillRoundedRect(0, 0, 14, 6, 3);
});
makeTexture(scene, TEXTURE_KEYS.bulletTurret, 14, 6, (g) => {
g.fillStyle(COLORS.turretBullet, 1);
g.fillRoundedRect(0, 0, 14, 6, 3);
});
makeTexture(scene, TEXTURE_KEYS.bulletEnemy, 12, 12, (g) => {
g.fillStyle(COLORS.enemyBullet, 1);
g.fillCircle(6, 6, 6);
g.fillStyle(0xffd0c0, 1);
g.fillCircle(6, 6, 2.5);
});
makeTexture(scene, TEXTURE_KEYS.turret, 48, 48, (g) => {
g.fillStyle(COLORS.navy, 1);
g.fillCircle(24, 24, 20);
g.lineStyle(3, COLORS.turret, 1);
g.strokeCircle(24, 24, 20);
g.fillStyle(COLORS.turret, 1);
g.fillRect(24, 20, 24, 8);
});
makeTexture(scene, TEXTURE_KEYS.supplyPod, 44, 44, (g) => {
// Kapsel mit Geschenkband – bewusst kein Kreuz (Rotkreuz-Zeichen ist geschützt).
g.fillStyle(COLORS.supply, 1);
g.fillRoundedRect(4, 4, 36, 36, 8);
g.fillStyle(COLORS.cream, 1);
g.fillRoundedRect(9, 9, 26, 26, 5);
g.fillStyle(COLORS.gold, 1);
g.fillRect(20, 9, 4, 26);
g.fillRect(9, 20, 26, 4);
g.fillCircle(22, 22, 4);
});
makeTexture(scene, TEXTURE_KEYS.ground, 128, 128, (g) => {
g.fillStyle(COLORS.ground, 1);
g.fillRect(0, 0, 128, 128);
g.fillStyle(COLORS.groundAlt, 1);
g.fillRect(0, 0, 64, 64);
g.fillRect(64, 64, 64, 64);
g.lineStyle(1, 0x000000, 0.18);
g.strokeRect(0, 0, 128, 128);
});
makeTexture(scene, TEXTURE_KEYS.particle, 8, 8, (g) => {
g.fillStyle(0xffffff, 1);
g.fillCircle(4, 4, 4);
});
makeTexture(scene, TEXTURE_KEYS.crosshair, 32, 32, (g) => {
g.lineStyle(2, COLORS.gold, 1);
g.strokeCircle(16, 16, 9);
g.lineBetween(16, 0, 16, 9);
g.lineBetween(16, 23, 16, 32);
g.lineBetween(0, 16, 9, 16);
g.lineBetween(23, 16, 32, 16);
});
makeTexture(scene, TEXTURE_KEYS.emblem, 160, 160, (g) => {
// Emblem der Vereinigten Erde: Planet mit Umlaufbahn (eigener Entwurf).
g.fillStyle(COLORS.navy, 1);
g.fillCircle(80, 80, 76);
g.lineStyle(6, COLORS.gold, 1);
g.strokeCircle(80, 80, 76);
g.fillStyle(COLORS.crimson, 1);
g.fillCircle(80, 80, 34);
g.lineStyle(6, COLORS.gold, 1);
g.strokeEllipse(80, 80, 132, 44);
g.fillStyle(COLORS.cream, 1);
g.fillCircle(136, 62, 8);
});
}