/** * binary companion star — a distant "other star" wandering slowly. * * Content layer (two world-space sprites: a bright core + a soft halo), * NOT a filter: Canvas-safe and immune to the HUD. It sits FAR away * (very low parallax ~0.08, so it barely moves as you fly — a distant * light source) and drifts along a slow elliptical arc, seeded per system * so each binary's companion wanders its own way. Both sprites use * additive blend — it is a source of light, so it glows, never occludes. * Depth 3 keeps it behind the art (planets 5, ship 10). * * Config: data/systems.json → types.binary.effect.wanderer. */ import Phaser from '../vendor/phaser.js'; import { canvasTexture } from '../utils/Textures.js'; import { toColor } from '../utils/Color.js'; const DEPTH = 3; // behind the art, in front of the starfield /** A tight, bright core — the star's body. White (tint gives the color). */ function drawCore(ctx, size) { const c = size / 2; const g = ctx.createRadialGradient(c, c, 0, c, c, c); g.addColorStop(0.0, 'rgba(255,255,255,1.0)'); g.addColorStop(0.5, 'rgba(255,255,255,0.9)'); g.addColorStop(1.0, 'rgba(255,255,255,0.0)'); ctx.fillStyle = g; ctx.fillRect(0, 0, size, size); } /** A wide, soft halo — the star's corona. White (tint gives the color). */ function drawHalo(ctx, size) { const c = size / 2; const g = ctx.createRadialGradient(c, c, 0, c, c, c); g.addColorStop(0.0, 'rgba(255,255,255,0.55)'); g.addColorStop(0.4, 'rgba(255,255,255,0.28)'); g.addColorStop(1.0, 'rgba(255,255,255,0.0)'); ctx.fillStyle = g; ctx.fillRect(0, 0, size, size); } /** * The companion star (core + halo) wandering a slow seeded arc. * * @param {object} scene Phaser scene * @param {string} color star color (CSS hex) * @param {object} cfg data/systems.json → effect.wanderer * @param {{phase?:number, angle?:number, drift?:number}} fx per-system seeded 0..1 variation */ export class WandererStar { constructor(scene, color, cfg, fx = {}) { this.scene = scene; this.color = toColor(color, 0x9fd0ff); this.cfg = cfg ?? {}; this.fx = fx ?? {}; this.core = null; this.halo = null; this.tSec = 0; } /** Build the core + halo (idempotent). */ create() { const scene = this.scene; if (this.core) return; const { coreSize = [40, 70], haloSize = [120, 200], parallax = 0.08, speed = [3, 8], } = this.cfg; const coreKey = canvasTexture(scene, 'fx-wanderer-core', 64, 64, (ctx) => drawCore(ctx, 64)); const haloKey = canvasTexture(scene, 'fx-wanderer-halo', 128, 128, (ctx) => drawHalo(ctx, 128)); // Seeded starting point (upper third of the view — a star in the sky) // and a slow elliptical arc (the "wander"). const fx = this.fx; const phase = fx.phase ?? 0.5; const angle = fx.angle ?? 0.5; const drift = fx.drift ?? 0.5; const w = scene.scale.width; const h = scene.scale.height; const baseX = w * (0.2 + 0.6 * phase); const baseY = h * (0.15 + 0.3 * angle); const ampX = w * (0.05 + 0.12 * drift); const ampY = h * (0.04 + 0.10 * drift); const omega = ((speed[0] + (speed[1] - speed[0]) * drift) / w) * Math.PI * 2; // rad/s (slow) this.core = scene.add .image(baseX, baseY, coreKey) .setDepth(DEPTH) .setTint(this.color) .setAlpha(0.95) .setBlendMode(Phaser.BlendModes.ADD); const cs = coreSize[0] + (coreSize[1] - coreSize[0]) * drift; this.core.setScale(cs / 64); this.halo = scene.add .image(baseX, baseY, haloKey) .setDepth(DEPTH) .setTint(this.color) .setAlpha(0.8) .setBlendMode(Phaser.BlendModes.ADD); const hs = haloSize[0] + (haloSize[1] - haloSize[0]) * drift; this.halo.setScale(hs / 128); this.baseX = baseX; this.baseY = baseY; this.ampX = ampX; this.ampY = ampY; this.omega = omega; this.phase0 = angle * Math.PI * 2; this.p = parallax; } /** Per-frame: slow elliptical wander + camera parallax. */ update(nowMs, dtMs) { if (!this.core) return; this.tSec += (dtMs || 0) / 1000; const t = this.tSec; const a = this.omega * t + this.phase0; let x = this.baseX + this.ampX * Math.cos(a); let y = this.baseY + this.ampY * Math.sin(a); // very low parallax (distant): shifts a little as the camera flies const cam = this.scene.cameras.main; const camX = cam?.worldView?.x ?? 0; const camY = cam?.worldView?.y ?? 0; x += (camX - this.scene.scale.width / 2) * this.p; y += (camY - this.scene.scale.height / 2) * this.p; this.core.setPosition(x, y); this.halo.setPosition(x, y); } /** Tear down. */ destroy() { this.core?.destroy(); this.halo?.destroy(); this.core = null; this.halo = null; } }