
- /* 單次動畫 */
- 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 回應:
張貼留言