Phase 3: AR hunt /hunt + launch page /play - shared-visual ghosts, colour lure, hauntometer
This commit is contained in:
+11
-10
@@ -29,15 +29,15 @@ export const GHOST_GRADIENTS = {
|
||||
|
||||
const WISP_VERT = `
|
||||
varying float vY;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPos;
|
||||
uniform float uTime;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
vec3 p = position;
|
||||
// gentle billow so the wisp breathes
|
||||
float w = sin(uTime * 1.6 + position.y * 3.0) * 0.04;
|
||||
p.x += w * (0.5 + uv.y);
|
||||
vY = uv.y;
|
||||
vPos = position; // object-space, for radial falloff in frag
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
||||
}
|
||||
`;
|
||||
@@ -45,17 +45,16 @@ const WISP_VERT = `
|
||||
const WISP_FRAG = `
|
||||
precision mediump float;
|
||||
varying float vY;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPos;
|
||||
uniform vec3 uTop;
|
||||
uniform vec3 uBottom;
|
||||
uniform float uAlpha;
|
||||
uniform float uTime;
|
||||
void main() {
|
||||
vec3 col = mix(uBottom, uTop, vY); // vertical gradient
|
||||
// soft radial falloff around the body centreline for a wispy edge
|
||||
float edge = smoothstep(0.5, 0.05, abs(vUv.x - 0.5));
|
||||
float fade = edge * (0.65 + 0.35 * sin(uTime * 2.0 + vY * 6.2831));
|
||||
float a = uAlpha * mix(0.45, 1.0, vY) * fade; // thinner at the tail
|
||||
vec3 col = mix(uBottom, uTop, vY); // vertical gradient (head bright)
|
||||
// breathing shimmer, never lets the body vanish
|
||||
float shimmer = 0.78 + 0.22 * sin(uTime * 2.0 + vY * 6.2831);
|
||||
float a = uAlpha * mix(0.55, 1.0, vY) * shimmer;
|
||||
gl_FragColor = vec4(col, a);
|
||||
}
|
||||
`;
|
||||
@@ -148,8 +147,10 @@ export function createGhostVisual(THREE, opts = {}) {
|
||||
function update(dt, elapsed) {
|
||||
uniforms.uTime.value = elapsed;
|
||||
stateT += dt;
|
||||
// bob
|
||||
group.position.y += Math.sin(elapsed * 1.8) * dt * 0.15;
|
||||
// bob: offset the BODY around the group, so the group's spawn position
|
||||
// stays fixed (previously this accumulated onto group.y and the ghost
|
||||
// drifted up out of view every frame).
|
||||
body.position.y = Math.sin(elapsed * 1.8) * 0.08;
|
||||
// billboard FX face camera handled by Sprite automatically
|
||||
if (state === 'spawn') {
|
||||
const k = Math.min(stateT / 0.6, 1);
|
||||
|
||||
+4
-2
@@ -24,7 +24,9 @@ const COLORS = ['Red', 'Blue', 'Yellow'];
|
||||
export function createHunt(THREE, scene, opts = {}) {
|
||||
const roster = opts.roster || [];
|
||||
const maxActive = opts.maxActive ?? 4;
|
||||
const spawnArcDeg = opts.spawnArcDeg ?? 120; // ±60°
|
||||
const spawnArcDeg = opts.spawnArcDeg ?? 90; // ±45° — tighter so ghosts land in front
|
||||
const fleeMinMs = opts.fleeMinMs ?? 12000; // longer discovery window
|
||||
const fleeMaxMs = opts.fleeMaxMs ?? 18000;
|
||||
const onEvent = opts.onEvent || (() => {});
|
||||
|
||||
let luredColor = null; // set by the colour wheel
|
||||
@@ -77,7 +79,7 @@ export function createHunt(THREE, scene, opts = {}) {
|
||||
active.set(id, {
|
||||
ghost: g, visual, hp, maxHp: hp,
|
||||
state: 'idle',
|
||||
fleeAt: performance.now() + (6000 + Math.random() * 4000), // escapes if ignored
|
||||
fleeAt: performance.now() + (fleeMinMs + Math.random() * (fleeMaxMs - fleeMinMs)), // escapes if ignored
|
||||
});
|
||||
haunt = Math.min(100, haunt + 4); // presence raises the meter
|
||||
onEvent({ type: 'spawn', id, ghost: g, haunt });
|
||||
|
||||
Reference in New Issue
Block a user