98 lines
3.8 KiB
JavaScript
98 lines
3.8 KiB
JavaScript
/**
|
||
* 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?.();
|
||
}
|
||
}
|