restore spawn-phase glow with a true breathing animation

SpawnOverlayPass had everything wired except a caller. WebGLFrameBuilder
now collects spawned human players each tick during spawn phase and
pushes their territory centroid + color through view.updateSpawnOverlay.
myPlayer reads as white so the local-player ring stands out.

Reshaped the shader animation: dropped the growing-disc effect, gave
the ring a true breath — radius scales 0.5×→1.15× while opacity pulses
35%→100% in phase. Replaced the sharp inner-edge ramp with a smooth
center-to-boundary fill so there's no hard cutoff or empty hole in
the middle. animSpeed bumped to 0.0035 (~1 breath/sec).
This commit is contained in:
evanpelle
2026-05-18 09:43:14 -07:00
parent 61f6d2fdd4
commit 4936ae3d59
3 changed files with 70 additions and 27 deletions
+46 -1
View File
@@ -1,7 +1,12 @@
import { Colord } from "colord";
import { PlayerType } from "../core/game/Game";
import { GameView } from "../core/game/GameView";
import { uploadFrameData } from "./render/frame/Upload";
import { PlayerStatic, GameView as WebGLGameView } from "./render/gl";
import {
PlayerStatic,
SpawnCenter,
GameView as WebGLGameView,
} from "./render/gl";
const PALETTE_SIZE = 4096;
@@ -31,6 +36,7 @@ export class WebGLFrameBuilder {
update(gameView: GameView): void {
this.syncPlayers(gameView);
this.syncLocalPlayer(gameView);
this.syncSpawnOverlay(gameView);
uploadFrameData(this.view, gameView.frameData());
}
@@ -41,6 +47,45 @@ export class WebGLFrameBuilder {
this.view.setLocalPlayerID(sid);
}
/**
* Spawn-phase highlights: each already-spawned human player gets a colored
* ring + tile glow around their starting territory. Pushed every tick
* during spawn phase; the pass animates locally from the snapshot.
*/
private syncSpawnOverlay(gameView: GameView): void {
const inSpawnPhase = gameView.inSpawnPhase();
if (!inSpawnPhase) {
this.view.updateSpawnOverlay(false, []);
return;
}
const me = gameView.myPlayer();
const myTeam = me?.team() ?? null;
const centers: SpawnCenter[] = [];
for (const p of gameView.players()) {
if (!p.isPlayer() || p.type() !== PlayerType.Human) continue;
if (!p.hasSpawned()) continue;
const isSelf = me !== null && p.smallID() === me.smallID();
// myPlayer reads as plain white so the local-player ring is visually
// distinct from any team color; everyone else uses their territory tint.
const c = isSelf
? { r: 255, g: 255, b: 255 }
: p.territoryColor().toRgb();
centers.push({
x: p.nameData?.x ?? 0,
y: p.nameData?.y ?? 0,
r: c.r / 255,
g: c.g / 255,
b: c.b / 255,
isSelf,
isTeammate:
myTeam !== null &&
p.team() === myTeam &&
p.smallID() !== me?.smallID(),
});
}
this.view.updateSpawnOverlay(true, centers);
}
private syncPlayers(gameView: GameView): void {
const newPlayers: PlayerStatic[] = [];
for (const p of gameView.players()) {