/* 單次動畫 */ function animateOnce(mixer, prop, dur, values) { /* 停止 & 清除 先前的動畫 */ mixer.stopAllAction(); mixer.uncacheRoot(mixer.getRoot()); /* 增加動畫片段 */ const track = new THREE.KeyframeTrack(prop, [0, dur], values); const clip = new THREE.AnimationClip('move', dur, [track]); const action = mixer.clipAction(clip); action.clampWhenFinished = true; action.setLoop(THREE.LoopOnce); /* Promise 來處理完成事件,這樣就可以用 await */ return new Promise((resolve) => { const finished = function () { mixer.removeEventListener('finished', finished); resolve(); }; mixer.addEventListener('finished', finished); action.play(); /* 播放動畫 */ }); }
想要能像 SVG 一樣簡單的控制模型的移動,ThreeJS 並沒有直接提供我想要的模式,試了很久終於成功了。
mixer 中的 action 是不斷添加的,這會混亂播放動畫,所以在播放新的動畫前必須[停止]且[清除]先前的動畫。
為了可以簡單串接不同模型的動畫,所以就想用 await 來達成,所以加上 Promise 的包裝。
完整程式
/* 初始化渲染器 */ const renderer = new THREE.WebGLRenderer({ antialias: true }); document.getElementById('Container').appendChild(renderer.domElement); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); /* 初始化場景 */ const scene = new THREE.Scene(); scene.background = new THREE.Color('#000'); /* 背景顏色 */ scene.add(new THREE.AmbientLight('#FFF', 0.5)); /* 加入環境光 */ scene.add(new THREE.AxesHelper(50)); /* 3D 軸標示 */ /* 初始化鏡頭 */ const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 300); camera.position.set(0, 4, 12); /* 初始化軌道控制,鏡頭的移動 */ const orbitControls = new THREE.OrbitControls(camera, renderer.domElement); orbitControls.update(); /* 動畫刷新器 */ const mixers = []; function newMixer(target) { const mixer = new THREE.AnimationMixer(target); mixers.push(mixer); return mixer; } /* 動畫計時器 */ const clock = new THREE.Clock(); /* 渲染週期 */ function renderCycle() { const delta = clock.getDelta(); mixers.forEach(x => x.update(delta)); renderer.render(scene, camera); requestAnimationFrame(renderCycle); } renderCycle(); /*---------------------------------------------------------------*/ /* 單次動畫 */ function animateOnce(mixer, prop, dur, values) { /* 停止 & 清除 先前的動畫 */ mixer.stopAllAction(); mixer.uncacheRoot(mixer.getRoot()); /* 增加動畫片段 */ const track = new THREE.KeyframeTrack(prop, [0, dur], values); const clip = new THREE.AnimationClip('move', dur, [track]); const action = mixer.clipAction(clip); action.clampWhenFinished = true; action.setLoop(THREE.LoopOnce); /* Promise 來處理完成事件,這樣就可以用 await */ return new Promise((resolve) => { const finished = function () { mixer.removeEventListener('finished', finished); resolve(); }; mixer.addEventListener('finished', finished); action.play(); /* 播放動畫 */ }); } const cubeA = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial( {color: '#0F0'} ) ); const mixerA = newMixer(cubeA); scene.add(cubeA); const cubeB = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial( {color: '#00F'} ) ); const mixerB = newMixer(cubeB); scene.add(cubeB); async function run() { await animateOnce(mixerA, '.position[x]', 3, [0, 3]); await animateOnce(mixerB, '.position[y]', 3, [0, 2]); await animateOnce(mixerA, '.position[x]', 3, [3, 0]); await animateOnce(mixerB, '.position[y]', 3, [2, 0]); } run();
0 回應:
張貼留言