在上一篇文章Three.js杂记(十四)—— 汽车展览·上 - 掘金 (juejin.cn)中主要对切换相机不同位置和鼠标拖拽移动相机焦点做了简单的应用。
那么现在聊聊该如何实现汽车模型自带的三种动画展示了,实际上可以是两种汽车前后盖打开
和汽车4车门打开
,最后一种只是将前两种结合起来了。
关于ThreeJs
中动画可以参考:
- 官方API文档:AnimationAction – three.js docs (threejs.org)
- ThreeJs中文网:1. 关键帧动画 | Three.js中文网 (webgl3d.cn)
模型动画运行
ThreeJs
动画是关键帧动画,在汽车模型导入时,可以从gltf
中查找到AnimationClip
动画剪辑。
在查找动画这一步上面,我卡了很久,一直没找到动画。原因是我从
gltf.scene
的animations
上开始查找的,没有找到,甚至还递归遍历内部元素寻找animations
。但是最后发现,直接gltf.animations
就可以找到三种动画,白白绕了一大圈。
找到模型动画后,使用AnimationMixer
函数生成一个动画混合器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
接下来将gltf.scene
关键帧动画模型的父对象作为参数传入。获得混合器实例mixer
,clipAction
方法获取获取动画剪辑,然后使用play
方法播放此动画。可以对循环次数进行限制。
在gltfLoader.load
中添加以下代码:
// 模型动画
const modelAnimationArr = gltf.animations;
//包含关键帧动画的模型作为参数创建一个播放器
mixer = new THREE.AnimationMixer(gltf.scene);
const clipAction = mixer.clipAction(modelAnimationArr[0]);
clipAction.play(); //播放动画
//不循环播放
clipAction.loop = THREE.LoopOnce;
当然,现在动画还是不会生效。因为没有执行混合器的update
方法,推进混合器时间并更新动画。需要在animate
中进行添加,可以使用ThreeJs
的Clock
时钟,用于跟踪时间,当然也可以直接使用new Date()
。
// 如果想播放动画,需要周期性执行`mixer.update()`更新AnimationMixer时间数据
const clock = new THREE.Clock();
function animate() {if (mixer) {//clock.getDelta()方法获得loop()两次执行时间间隔let frameT = clock.getDelta();// 更新播放器相关的时间mixer.update(frameT);}// ...
}
这样一来,汽车第一个动画效果就运行起来了。
三种动画切换
在成功运行了第一种动画后,接下来就只是一些完善性的工作了。
在原先的面板上添加三种动画的切换按钮吧。
然后动画的切换比较重要,这里官网API:AnimationAction – three.js docs (threejs.org)
- 对于当前所属动画可以设置
curIndex
,然后如果动画尚未结束,那么先用stop
方法结束动画 - 判断动画是否结束,可以在混合器上绑定
finished
监听事件。在动画事件结束之后,重新将curIndex
设置为-1 - 动画启动时,设置
curIndex
的值为当前动画的index序号,然后使用play
函数进行播放。 - 根据我的观察,当前汽车模型的动画最后会把打开的车门又关闭,但是我不希望关闭,可以设置剪辑对象的结束时间,并通过
clampWhenFinished
让动画停止在最后一帧。
// 切换改变当前汽车动画
let curIndex = -1;
const changeAnimation = (num: number) => {if (curIndex > -1) {// 结束原先动画mixer.clipAction(modelAnimationArr[curIndex]).stop();}curIndex = num;const clipAction = mixer.clipAction(modelAnimationArr[num]);clipAction.time = 2; // 剪辑对象起始时间clipAction._clip.duration = 10; //剪辑对象设置播放结束时间clipAction.clampWhenFinished = true; // 是否在动画结束时停止播放clipAction.play(); //播放动画//不循环播放clipAction.loop = THREE.LoopOnce; mixer.addEventListener( 'finished', function(e:any) {curIndex = -1;});
}
效果: