213 lines
6.8 KiB
JavaScript
213 lines
6.8 KiB
JavaScript
/**
|
||
* 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);
|
||
});
|
||
}
|