Phase 3 update: orbit ghosts, parallax, FX, shader fix [build v8]
This commit is contained in:
+31
-12
@@ -27,6 +27,7 @@ export function createHunt(THREE, scene, opts = {}) {
|
||||
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 fxTextures = opts.fxTextures || null; // {glow, trail, smoke} THREE.Textures
|
||||
const onEvent = opts.onEvent || (() => {});
|
||||
|
||||
let luredColor = null; // set by the colour wheel
|
||||
@@ -55,15 +56,29 @@ export function createHunt(THREE, scene, opts = {}) {
|
||||
return pool[pool.length - 1][0];
|
||||
}
|
||||
|
||||
function placeInArc(obj) {
|
||||
const half = (spawnArcDeg * Math.PI / 180) / 2;
|
||||
const yaw = (Math.random() * 2 - 1) * half; // forward-facing arc
|
||||
const pitch = (Math.random() * 0.5 - 0.15); // slightly varied height
|
||||
const dist = 2.2 + Math.random() * 1.8; // 2.2-4m out
|
||||
obj.position.set(
|
||||
Math.sin(yaw) * dist,
|
||||
0.2 + Math.sin(pitch) * 1.2,
|
||||
-Math.cos(yaw) * dist
|
||||
function placeOnRing(a) {
|
||||
// Full 360° ring around the stationary player at roughly eye level.
|
||||
// Each ghost gets orbit params so it slowly circles + bobs; the player
|
||||
// stands still and pans to track them (works on iOS, no walking needed).
|
||||
a.azimuth = Math.random() * Math.PI * 2; // start anywhere around
|
||||
a.radius = 2.6 + Math.random() * 1.6; // 2.6–4.2m out
|
||||
a.height = -0.3 + Math.random() * 1.0; // around eye level
|
||||
a.orbitSpeed = (Math.random() < 0.5 ? -1 : 1) * (0.05 + Math.random() * 0.12); // rad/s, either direction
|
||||
a.bobPhase = Math.random() * Math.PI * 2;
|
||||
a.bobAmp = 0.1 + Math.random() * 0.2;
|
||||
a.radiusPhase = Math.random() * Math.PI * 2;
|
||||
a.radiusAmp = 0.2 + Math.random() * 0.4; // gentle in/out drift
|
||||
positionFromOrbit(a, 0);
|
||||
}
|
||||
|
||||
function positionFromOrbit(a, elapsed) {
|
||||
const az = a.azimuth + a.orbitSpeed * elapsed;
|
||||
const r = a.radius + Math.sin(elapsed * 0.5 + a.radiusPhase) * a.radiusAmp;
|
||||
const y = a.height + Math.sin(elapsed * 1.2 + a.bobPhase) * a.bobAmp;
|
||||
a.visual.object3d.position.set(
|
||||
Math.sin(az) * r,
|
||||
y,
|
||||
-Math.cos(az) * r
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,16 +86,18 @@ export function createHunt(THREE, scene, opts = {}) {
|
||||
if (active.size >= maxActive) return;
|
||||
const g = pickGhost();
|
||||
const visual = createGhostVisual(THREE, { color: g.color, fx: opts.fx !== false });
|
||||
placeInArc(visual.object3d);
|
||||
if (fxTextures) visual.attachFx(fxTextures);
|
||||
scene.add(visual.object3d);
|
||||
const id = nextId++;
|
||||
// hp derived from health pips/stat; charge to capture scales with chargePips
|
||||
const hp = (g.healthMax || 300) / 100; // ~2.6-4.5
|
||||
active.set(id, {
|
||||
const a = {
|
||||
ghost: g, visual, hp, maxHp: hp,
|
||||
state: 'idle',
|
||||
fleeAt: performance.now() + (fleeMinMs + Math.random() * (fleeMaxMs - fleeMinMs)), // escapes if ignored
|
||||
});
|
||||
};
|
||||
placeOnRing(a); // assign orbit + initial position
|
||||
active.set(id, a);
|
||||
haunt = Math.min(100, haunt + 4); // presence raises the meter
|
||||
const p = visual.object3d.position;
|
||||
onEvent({ type: 'spawn', id, ghost: g, haunt, pos: { x: +p.x.toFixed(2), y: +p.y.toFixed(2), z: +p.z.toFixed(2) } });
|
||||
@@ -137,6 +154,8 @@ export function createHunt(THREE, scene, opts = {}) {
|
||||
|
||||
const now = performance.now();
|
||||
for (const [id, a] of active) {
|
||||
// orbit-and-drift around the stationary player
|
||||
if (a.state !== 'capture') positionFromOrbit(a, elapsed);
|
||||
a.visual.update(dt, elapsed);
|
||||
if (now >= a.fleeAt) flee(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user