mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:20:45 +00:00
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:
@@ -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()) {
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
"selfMaxRad": 24,
|
||||
"mateMinRad": 5,
|
||||
"mateMaxRad": 14,
|
||||
"animSpeed": 0.005,
|
||||
"animSpeed": 0.0035,
|
||||
"gradientInnerEdge": 0.01,
|
||||
"gradientSolidEnd": 0.1
|
||||
},
|
||||
|
||||
@@ -67,34 +67,32 @@ void main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Static outer ring: radial gradient from minR to maxR
|
||||
float range = maxR - minR;
|
||||
float t = (dist - minR) / range;
|
||||
if (t > 0.0 && t <= 1.0) {
|
||||
float innerEdge = uGradientStops.x;
|
||||
float solidEnd = uGradientStops.y;
|
||||
float alpha;
|
||||
if (t < innerEdge) {
|
||||
alpha = t / innerEdge;
|
||||
} else if (t < solidEnd) {
|
||||
alpha = 1.0;
|
||||
} else {
|
||||
alpha = 1.0 - (t - solidEnd) / (1.0 - solidEnd);
|
||||
}
|
||||
|
||||
// Breathing ring: the gradient halo shrinks/expands in radius AND its
|
||||
// opacity pulses in phase with the breath — both driven by uBreathRadius.
|
||||
// Smooth bell shape: glow ramps up from center to the inner edge, stays
|
||||
// solid through the ring's body, then fades out past solidEnd. No hard
|
||||
// cutoffs at either side.
|
||||
float scale = 0.5 + 0.65 * uBreathRadius; // 0.5 → 1.15 of base radius
|
||||
float bMinR = minR * scale;
|
||||
float bMaxR = maxR * scale;
|
||||
float range = bMaxR - bMinR;
|
||||
float t = (dist - bMinR) / range;
|
||||
float solidEnd = uGradientStops.y;
|
||||
float alpha = 0.0;
|
||||
if (dist < bMinR) {
|
||||
// Inner glow: linear ramp from 0 at center to 1 at the ring's inner edge.
|
||||
alpha = dist / max(bMinR, 0.001);
|
||||
} else if (t < solidEnd) {
|
||||
alpha = 1.0;
|
||||
} else if (t < 1.0) {
|
||||
alpha = 1.0 - (t - solidEnd) / (1.0 - solidEnd);
|
||||
}
|
||||
if (alpha > 0.0) {
|
||||
// Opacity pulses 35% → 100% in phase with the radius.
|
||||
alpha *= 0.35 + 0.65 * uBreathRadius;
|
||||
result.rgb = mix(result.rgb, color, alpha * (1.0 - result.a));
|
||||
result.a = result.a + alpha * (1.0 - result.a);
|
||||
}
|
||||
|
||||
// Breathing ring: solid colored disc from minR to breathR
|
||||
float breathR = minR + range * uBreathRadius;
|
||||
if (breathR > minR + 0.01) {
|
||||
if (dist >= minR && dist <= breathR) {
|
||||
float edge = smoothstep(minR, minR + 0.1, dist);
|
||||
result.rgb = color;
|
||||
result.a = max(result.a, edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.a < 0.001) discard;
|
||||
|
||||
Reference in New Issue
Block a user