Phase 3 update: orbit ghosts, parallax, FX, shader fix [build v8]

This commit is contained in:
2026-06-23 15:35:55 +10:00
parent 802ae7fab9
commit ce35712014
2 changed files with 104 additions and 19 deletions
+24 -8
View File
@@ -203,7 +203,7 @@
<button class="btn ghost" id="go-gyro">No AR - use motion</button>
</div>
<div id="note">Fan-made tribute. Not affiliated with or endorsed by the LEGO Group.</div>
<div id="ver" style="position:fixed;right:6px;bottom:6px;z-index:11;font-size:9px;color:#2e3340;letter-spacing:0.1em;">build v8</div>
<div id="ver" style="position:fixed;right:6px;bottom:6px;z-index:11;font-size:9px;color:#2e3340;letter-spacing:0.1em;">build v9</div>
<script type="importmap">
{ "imports": {
@@ -213,14 +213,15 @@
</script>
<script type="module">
import * as THREE from 'three';
import { createGhostVisual } from './ghost-visual.js?v=8';
import { createHunt } from './hunt.js?v=8';
console.log('%cNewbury Nights hunt — build v8 loaded (orbit ghosts + parallax)', 'color:#51EAF1');
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
import { createGhostVisual, setSharedMesh, setSharedMeshYRange } from './ghost-visual.js?v=9';
import { createHunt } from './hunt.js?v=9';
console.log('%cNewbury Nights hunt — build v9 loaded (real ghost mesh)', 'color:#51EAF1');
const $ = s => document.querySelector(s);
let roster = [];
try {
roster = await (await fetch('./ghosts.enriched.json?v=8')).json();
roster = await (await fetch('./ghosts.enriched.json?v=9')).json();
} catch (e) {
roster = [
{ id:'basic-r', name:'Wisp', color:'Red', rarity:'Common', rarityTier:1, chargePips:2, healthMax:263 },
@@ -244,12 +245,27 @@ function loadTex(path){
return new Promise(res => texLoader.load(path, t => res(t), undefined, () => res(null)));
}
const fxTextures = {
glow: await loadTex('./ghosts/fx/FX_Glow.png?v=8'),
trail: await loadTex('./ghosts/fx/FX_WispyTrail2.png?v=8'),
smoke: await loadTex('./ghosts/fx/smokepuff1.png?v=8'),
glow: await loadTex('./ghosts/fx/FX_Glow.png?v=9'),
trail: await loadTex('./ghosts/fx/FX_WispyTrail2.png?v=9'),
smoke: await loadTex('./ghosts/fx/smokepuff1.png?v=9'),
};
console.log('FX textures loaded:', Object.fromEntries(Object.entries(fxTextures).map(([k,v])=>[k, !!v])));
// ---- load the real ghost mesh (Ghost_Bat) used as the body for all ghosts ----
// Recoloured per-ghost by the gradient. Falls back to procedural wisp if it fails.
try {
const objLoader = new OBJLoader();
const meshObj = await new Promise((res, rej) =>
objLoader.load('./ghosts/mesh/Ghost_Bat.obj?v=9', res, undefined, rej));
// compute the mesh's Y range so the gradient maps head->tail correctly
const box = new THREE.Box3().setFromObject(meshObj);
setSharedMeshYRange(box.min.y, box.max.y);
setSharedMesh(meshObj);
console.log('Ghost mesh loaded: Ghost_Bat Y[' + box.min.y.toFixed(2) + ',' + box.max.y.toFixed(2) + ']');
} catch (err) {
console.warn('Ghost mesh failed to load, using procedural wisp:', err?.message || err);
}
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth/innerHeight; camera.updateProjectionMatrix();