orbit/js/entities/RoutePulse.js

98 lines
3.8 KiB
JavaScript
Raw 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.

/**
* RoutePulse — the route-target "target" halo: the in-world
* "your destination is HERE" mark (the route's orange).
*
* Shape: a steady target ring at `baseRadius` × the object's keepout
* radius, plus two radar ripples (staggered half a period) that expand
* a further ×`expand` past it and fade out. All the geometry scales with
* the object's keepout radius, so it reads the same on a small waypoint
* beacon and a gas giant.
*
* Who wears it (GameScene.update marks exactly one object per frame —
* the route's NEXT STOP):
* · the ROUTE GATE (the destination's waypoint in an intermediate
* system — GameScene.routeNextGate) — the FULL pulse
* (data/gates.json → route.pulse), via JumpGate.setRouteTarget
* · the DESTINATION OBJECT in the destination system (a planet or a
* space station — the route's final stop —
* GameScene._routeDestinationEntity) — the SUBDUED pulse
* (data/gates.json → route.objectPulse), via
* Planet.setRouteTarget / Station.setRouteTarget
* The compass's orange arrow is the off-screen half; the pulse is what
* the player sees the moment the target comes on screen.
*
* Not a scene object of its own — the host entity places `pulse.ring`
* (a scene Graphics) where it belongs: as a child at (0, 0) for
* container hosts (gate / station), or beside the object for a Sprite
* host (planet — static, so no following is needed), and drives
* `pulse.draw(t)` from its own update() (the scene's update loop drives
* the entity, as with the gate's swirl or the station's beacon).
*/
export class RoutePulse {
/**
* @param {Phaser.Scene} scene
* @param {object} o {
* size — the object's keepout radius (px) — the geometry's scale,
* cfg — { baseRadius, expand, period, ringAlpha, rippleAlpha }
* (a data/gates.json section — route.pulse / route.objectPulse),
* color — the color int (the route's orange — route.compassColor),
* x, y — the ring's position (0, 0 container-local — or the
* object's center for a Sprite host),
* depth — the ring's depth (the host's world-object depth),
* }
*/
constructor(scene, o) {
this.size = o.size;
this.cfg = o.cfg ?? {};
this.color = o.color ?? 0xff8c1a;
this.on = false;
this.ring = scene.add.graphics().setPosition(o.x ?? 0, o.y ?? 0);
if (o.depth !== undefined) this.ring.setDepth(o.depth);
this.ring.visible = false; // hidden until claimed (set(true))
}
/**
* Show / hide the pulse (a no-op when unchanged). Hiding clears the
* strokes so no stale frame lingers.
*/
set(on) {
const want = on === true;
if (this.on === want) return;
this.on = want;
if (!want) {
this.ring.clear();
this.ring.visible = false;
} else {
this.ring.visible = true;
}
}
/** Draw the pulse at time t (seconds). A no-op while hidden. */
draw(t) {
if (!this.on) return;
const g = this.ring;
g.clear();
const S = this.size;
const base = Math.max(2, S * 0.035);
const R0 = S * (this.cfg.baseRadius ?? 1.18);
const period = this.cfg.period ?? 1.6;
const expand = this.cfg.expand ?? 0.55;
const aRing = this.cfg.ringAlpha ?? 0.5;
const aRipple = this.cfg.rippleAlpha ?? 0.85;
// The steady ring — the circle the ripples grow out of.
g.lineStyle(base, this.color, aRing);
g.strokeCircle(0, 0, R0);
// The ripples — two, half a period apart.
for (let k = 0; k < 2; k++) {
const phase = (t / period + k * 0.5) % 1;
g.lineStyle(Math.max(1, base * (1 - 0.6 * phase)), this.color, aRipple * (1 - phase));
g.strokeCircle(0, 0, R0 * (1 + expand * phase));
}
}
/** Tear the ring down (the host is destroyed). */
destroy() {
this.ring.destroy?.();
}
}