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 = `
|
const WISP_VERT = `
|
||||||
varying float vY;
|
varying float vY;
|
||||||
varying vec2 vUv;
|
varying vec3 vPos;
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
void main() {
|
void main() {
|
||||||
vUv = uv;
|
|
||||||
vec3 p = position;
|
vec3 p = position;
|
||||||
// gentle billow so the wisp breathes
|
// gentle billow so the wisp breathes
|
||||||
float w = sin(uTime * 1.6 + position.y * 3.0) * 0.04;
|
float w = sin(uTime * 1.6 + position.y * 3.0) * 0.04;
|
||||||
p.x += w * (0.5 + uv.y);
|
p.x += w * (0.5 + uv.y);
|
||||||
vY = uv.y;
|
vY = uv.y;
|
||||||
|
vPos = position; // object-space, for radial falloff in frag
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -45,17 +45,16 @@ const WISP_VERT = `
|
|||||||
const WISP_FRAG = `
|
const WISP_FRAG = `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying float vY;
|
varying float vY;
|
||||||
varying vec2 vUv;
|
varying vec3 vPos;
|
||||||
uniform vec3 uTop;
|
uniform vec3 uTop;
|
||||||
uniform vec3 uBottom;
|
uniform vec3 uBottom;
|
||||||
uniform float uAlpha;
|
uniform float uAlpha;
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
void main() {
|
void main() {
|
||||||
vec3 col = mix(uBottom, uTop, vY); // vertical gradient
|
vec3 col = mix(uBottom, uTop, vY); // vertical gradient (head bright)
|
||||||
// soft radial falloff around the body centreline for a wispy edge
|
// breathing shimmer, never lets the body vanish
|
||||||
float edge = smoothstep(0.5, 0.05, abs(vUv.x - 0.5));
|
float shimmer = 0.78 + 0.22 * sin(uTime * 2.0 + vY * 6.2831);
|
||||||
float fade = edge * (0.65 + 0.35 * sin(uTime * 2.0 + vY * 6.2831));
|
float a = uAlpha * mix(0.55, 1.0, vY) * shimmer;
|
||||||
float a = uAlpha * mix(0.45, 1.0, vY) * fade; // thinner at the tail
|
|
||||||
gl_FragColor = vec4(col, a);
|
gl_FragColor = vec4(col, a);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -148,8 +147,10 @@ export function createGhostVisual(THREE, opts = {}) {
|
|||||||
function update(dt, elapsed) {
|
function update(dt, elapsed) {
|
||||||
uniforms.uTime.value = elapsed;
|
uniforms.uTime.value = elapsed;
|
||||||
stateT += dt;
|
stateT += dt;
|
||||||
// bob
|
// bob: offset the BODY around the group, so the group's spawn position
|
||||||
group.position.y += Math.sin(elapsed * 1.8) * dt * 0.15;
|
// 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
|
// billboard FX face camera handled by Sprite automatically
|
||||||
if (state === 'spawn') {
|
if (state === 'spawn') {
|
||||||
const k = Math.min(stateT / 0.6, 1);
|
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 = {}) {
|
export function createHunt(THREE, scene, opts = {}) {
|
||||||
const roster = opts.roster || [];
|
const roster = opts.roster || [];
|
||||||
const maxActive = opts.maxActive ?? 4;
|
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 || (() => {});
|
const onEvent = opts.onEvent || (() => {});
|
||||||
|
|
||||||
let luredColor = null; // set by the colour wheel
|
let luredColor = null; // set by the colour wheel
|
||||||
@@ -77,7 +79,7 @@ export function createHunt(THREE, scene, opts = {}) {
|
|||||||
active.set(id, {
|
active.set(id, {
|
||||||
ghost: g, visual, hp, maxHp: hp,
|
ghost: g, visual, hp, maxHp: hp,
|
||||||
state: 'idle',
|
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
|
haunt = Math.min(100, haunt + 4); // presence raises the meter
|
||||||
onEvent({ type: 'spawn', id, ghost: g, haunt });
|
onEvent({ type: 'spawn', id, ghost: g, haunt });
|
||||||
|
|||||||
Reference in New Issue
Block a user