2023-03-07 16:55

[ThreeJS] 單次動畫

  1. /* 單次動畫 */ 
  2. function animateOnce(mixer, prop, dur, values) { 
  3.    /* 停止 & 清除 先前的動畫 */ 
  4.    mixer.stopAllAction(); 
  5.    mixer.uncacheRoot(mixer.getRoot()); 
  6.  
  7.    /* 增加動畫片段 */ 
  8.    const track = new THREE.KeyframeTrack(prop, [0, dur], values); 
  9.    const clip = new THREE.AnimationClip('move', dur, [track]); 
  10.    const action = mixer.clipAction(clip); 
  11.    action.clampWhenFinished = true; 
  12.    action.setLoop(THREE.LoopOnce); 
  13.  
  14.    /* Promise 來處理完成事件,這樣就可以用 await */ 
  15.    return new Promise((resolve) => { 
  16.        const finished = function () { 
  17.            mixer.removeEventListener('finished', finished); 
  18.            resolve(); 
  19.        }; 
  20.        mixer.addEventListener('finished', finished); 
  21.  
  22.        action.play(); /* 播放動畫 */ 
  23.    }); 
  24. } 

想要能像 SVG 一樣簡單的控制模型的移動,ThreeJS 並沒有直接提供我想要的模式,試了很久終於成功了。

mixer 中的 action 是不斷添加的,這會混亂播放動畫,所以在播放新的動畫前必須[停止]且[清除]先前的動畫。

為了可以簡單串接不同模型的動畫,所以就想用 await 來達成,所以加上 Promise 的包裝。

完整程式

  1. /* 初始化渲染器 */ 
  2. const renderer = new THREE.WebGLRenderer({ antialias: true }); 
  3. document.getElementById('Container').appendChild(renderer.domElement); 
  4. renderer.setPixelRatio(window.devicePixelRatio); 
  5. renderer.setSize(window.innerWidth, window.innerHeight); 
  6.  
  7. /* 初始化場景 */ 
  8. const scene = new THREE.Scene(); 
  9. scene.background = new THREE.Color('#000'); /* 背景顏色 */ 
  10. scene.add(new THREE.AmbientLight('#FFF', 0.5)); /* 加入環境光 */ 
  11. scene.add(new THREE.AxesHelper(50)); /* 3D 軸標示 */ 
  12.  
  13. /* 初始化鏡頭 */ 
  14. const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 300); 
  15. camera.position.set(0, 4, 12); 
  16.  
  17. /* 初始化軌道控制,鏡頭的移動 */ 
  18. const orbitControls = new THREE.OrbitControls(camera, renderer.domElement); 
  19. orbitControls.update(); 
  20.  
  21.  
  22. /* 動畫刷新器 */ 
  23. const mixers = []; 
  24.  
  25. function newMixer(target) { 
  26.    const mixer = new THREE.AnimationMixer(target); 
  27.    mixers.push(mixer); 
  28.    return mixer; 
  29. } 
  30.  
  31. /* 動畫計時器 */ 
  32. const clock = new THREE.Clock(); 
  33.  
  34. /* 渲染週期 */ 
  35. function renderCycle() { 
  36.    const delta = clock.getDelta(); 
  37.    mixers.forEach(x => x.update(delta)); 
  38.  
  39.    renderer.render(scene, camera); 
  40.    requestAnimationFrame(renderCycle); 
  41. } 
  42. renderCycle(); 
  43.  
  44.  
  45.  
  46. /*---------------------------------------------------------------*/ 
  47.  
  48. /* 單次動畫 */ 
  49. function animateOnce(mixer, prop, dur, values) { 
  50.    /* 停止 & 清除 先前的動畫 */ 
  51.    mixer.stopAllAction(); 
  52.    mixer.uncacheRoot(mixer.getRoot()); 
  53.  
  54.    /* 增加動畫片段 */ 
  55.    const track = new THREE.KeyframeTrack(prop, [0, dur], values); 
  56.    const clip = new THREE.AnimationClip('move', dur, [track]); 
  57.    const action = mixer.clipAction(clip); 
  58.    action.clampWhenFinished = true; 
  59.    action.setLoop(THREE.LoopOnce); 
  60.  
  61.    /* Promise 來處理完成事件,這樣就可以用 await */ 
  62.    return new Promise((resolve) => { 
  63.        const finished = function () { 
  64.            mixer.removeEventListener('finished', finished); 
  65.            resolve(); 
  66.        }; 
  67.        mixer.addEventListener('finished', finished); 
  68.  
  69.        action.play(); /* 播放動畫 */ 
  70.    }); 
  71. } 
  72.  
  73.  
  74. const cubeA = new THREE.Mesh( 
  75.    new THREE.BoxGeometry(1, 1, 1), 
  76.    new THREE.MeshBasicMaterial( {color: '#0F0'} ) 
  77. ); 
  78. const mixerA = newMixer(cubeA); 
  79. scene.add(cubeA); 
  80.  
  81.  
  82. const cubeB = new THREE.Mesh( 
  83.    new THREE.BoxGeometry(1, 1, 1), 
  84.    new THREE.MeshBasicMaterial( {color: '#00F'} ) 
  85. ); 
  86. const mixerB = newMixer(cubeB); 
  87. scene.add(cubeB); 
  88.  
  89.  
  90. async function run() { 
  91.    await animateOnce(mixerA, '.position[x]', 3, [0, 3]); 
  92.    await animateOnce(mixerB, '.position[y]', 3, [0, 2]); 
  93.    await animateOnce(mixerA, '.position[x]', 3, [3, 0]); 
  94.    await animateOnce(mixerB, '.position[y]', 3, [2, 0]); 
  95. } 
  96.  
  97. run(); 

0 回應: