AshenDepths / animtest.html
Quazim0t0's picture
Deploy Ashen Depths: player animated by skeleton_animator.pt
ee5ec19 verified
Raw
History Blame Contribute Delete
3.85 kB
<!DOCTYPE html><html><head><meta charset="utf-8"><title>anim test</title>
<style>html,body{margin:0;height:100%;background:#202028;overflow:hidden}
#hud{position:fixed;top:0;left:0;color:#0f0;font:12px monospace;z-index:9;padding:6px;background:#000a;white-space:pre}</style>
<script type="importmap">{"imports":{
"three":"https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/",
"fflate":"https://unpkg.com/fflate@0.8.2/esm/browser.js"
}}</script></head>
<body><div id="hud">loading…</div>
<script type="module">
import * as THREE from 'three';
import {FBXLoader} from 'three/addons/loaders/FBXLoader.js';
const hud=document.getElementById('hud');const L=(m)=>hud.textContent=m;
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,1.5));renderer.setSize(innerWidth,innerHeight);
renderer.shadowMap.enabled=true;document.body.appendChild(renderer.domElement);
const scene=new THREE.Scene();scene.background=new THREE.Color(0x202028);
scene.add(new THREE.HemisphereLight(0xffffff,0x445,1.2));
const dl=new THREE.DirectionalLight(0xffffff,2.2);dl.position.set(3,8,6);dl.castShadow=true;scene.add(dl);
const ground=new THREE.Mesh(new THREE.PlaneGeometry(40,40),new THREE.MeshStandardMaterial({color:0x33343e}));
ground.rotation.x=-Math.PI/2;ground.receiveShadow=true;scene.add(ground);
const cam=new THREE.PerspectiveCamera(45,innerWidth/innerHeight,0.05,100);
cam.position.set(0,1.1,3.6);cam.lookAt(0,1.0,0);
scene.overrideMaterial=new THREE.MeshStandardMaterial({color:0xbfc4cc,roughness:.7});
const fbx=new FBXLoader();
const load=p=>new Promise((res,rej)=>fbx.load(p,res,undefined,rej));
const WHICH=location.hash==='#boss'?'boss':'player';
const BASE=WHICH==='boss'?'assets/chars/malenia.fbx':'assets/chars/player.fbx';
const NAMES=WHICH==='boss'
?['idle','walk','run','attack1','attack2','kick','block','powerup','hit','death']
:['idle','walk','walkback','run','attack1','attack2','combo','kick','block','hit','roll'];
let mixer,clips={},cur,action;
window.__clip=(n)=>{if(!clips[n])return 'no '+n;const na=mixer.clipAction(clips[n]);
if(action&&action!==na){action.fadeOut(0.2);}na.reset().fadeIn(0.2).play();action=na;cur=n;return {n,dur:+clips[n].duration.toFixed(2)};};
window.__list=()=>Object.keys(clips).map(n=>({n,dur:+clips[n].duration.toFixed(2)}));
(async()=>{
L('loading base '+WHICH+'…');
const base=await load(BASE);
const box=new THREE.Box3().setFromObject(base);const s=new THREE.Vector3();box.getSize(s);
const sc=1.8/s.y;base.scale.setScalar(sc);base.position.y=-box.min.y*sc;
base.traverse(o=>{if(o.isMesh){o.castShadow=true;o.frustumCulled=false;}});
scene.add(base);
mixer=new THREE.AnimationMixer(base);
const boneNames=new Set();base.traverse(o=>{if(o.isBone)boneNames.add(o.name);});
for(const n of NAMES){
try{const a=await load('assets/anim/'+WHICH+'/'+n+'.fbx');
if(a.animations&&a.animations[0]){clips[n]=a.animations[0];clips[n].name=n;}
else L('no clip in '+n);
}catch(e){console.warn(n,e);}
}
// report track/bone match on first clip
const first=clips[NAMES[0]];
let matched=0,total=0;
if(first){for(const tr of first.tracks){total++;const bn=tr.name.split('.')[0];if(boneNames.has(bn))matched++;}}
L(`base bones=${boneNames.size} clips=${Object.keys(clips).length} tracksMatched=${matched}/${total}\n__clip(n) __list()`);
window.__loaded={bones:boneNames.size,clips:Object.keys(clips),matched,total};
if(clips.idle)window.__clip('idle');
let last=performance.now();
(function loop(now){requestAnimationFrame(loop);const dt=Math.min((now-last)/1000,0.05);last=now;
if(mixer)mixer.update(dt);renderer.render(scene,cam);})(performance.now());
})().catch(e=>L('ERR '+e.message+'\n'+(e.stack||'')));
</script></body></html>