Three.js:射线检测物体时如何控制动画剪辑
解决Three.js点击JSON加载模型播放动画的问题
嘿,我一眼就看出你代码里的问题了——你调用intersects[0].clipAction(...)是找错对象啦!intersects[0]是射线检测返回的Intersection实例,它只是个包含被点击物体信息的包装对象,而clipAction()是AnimationMixer的方法,不是它的。咱们一步步来把这个功能搞定:
1. 加载模型时绑定动画资源
用JSONLoader加载带动画的模型时,得给每个模型配上对应的AnimationMixer,还要把动画剪辑存好,这样点击的时候才能直接调用:
// 初始化JSONLoader(要是你用的是新版本Three.js,其实更推荐GLTFLoader,但逻辑是通的) const jsonLoader = new THREE.JSONLoader(); jsonLoader.load('你的模型路径.json', function(geometry, materials) { // 创建蒙皮网格(带骨骼动画的JSON模型必须用SkinnedMesh) const mesh = new THREE.SkinnedMesh(geometry, materials); // 给模型绑定AnimationMixer,后续靠它控制动画 mesh.mixer = new THREE.AnimationMixer(mesh); // 把模型自带的动画剪辑存到mesh上,方便后续取用 mesh.animations = geometry.animations; // 把模型加到场景里 scene.add(mesh); });
2. 点击时正确触发动画
在你的点击事件处理函数里,先从射线检测结果里拿到真正被点击的模型对象,再用它绑定的mixer来创建动画动作:
function handleMouseClick(event) { // 常规射线检测逻辑(你已经实现了,这里快速过一遍) const raycaster = new THREE.Raycaster(); const mousePos = new THREE.Vector2(); // 把鼠标坐标转换成Three.js需要的归一化坐标 mousePos.x = (event.clientX / window.innerWidth) * 2 - 1; mousePos.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mousePos, camera); const hits = raycaster.intersectObjects(scene.children, true); if (hits.length > 0) { const clickedModel = hits[0].object; // 先确认这个模型有绑定动画资源 if (clickedModel.mixer && clickedModel.animations.length > 0) { // 先停掉之前的动画(如果需要的话,避免多个动画叠加) clickedModel.mixer.stopAllAction(); // 创建动画动作、设置时长然后播放 const animAction = clickedModel.mixer.clipAction(clickedModel.animations[0]); animAction.setDuration(4); animAction.play(); } } } // 记得给页面绑定点击事件 window.addEventListener('click', handleMouseClick);
3. 关键!别忘了更新动画混合器
这一步90%的新手都会忘——必须在渲染循环里更新所有的AnimationMixer,不然动画根本不会动:
const clock = new THREE.Clock(); function animateLoop() { requestAnimationFrame(animateLoop); // 获取每帧的时间差,用来更新动画进度 const deltaTime = clock.getDelta(); // 遍历场景里的所有对象,更新带mixer的模型的动画 scene.traverse(obj => { if (obj.mixer) { obj.mixer.update(deltaTime); } }); renderer.render(scene, camera); } animateLoop();
额外提醒
- 如果你的模型是层级结构(比如一个角色由多个Mesh组成),射线检测到的可能是子Mesh,这时候你得向上遍历父对象,找到带有
mixer属性的那个根模型。比如可以写个小函数:
然后把function findAnimatedParent(obj) { while (obj && !obj.mixer) { obj = obj.parent; } return obj; }const clickedModel = hits[0].object;改成const clickedModel = findAnimatedParent(hits[0].object); - 可以用
console.log(geometry.animations)检查一下你的JSON模型是不是真的带了动画数据,要是为空的话,那就是模型本身的问题啦。
内容的提问来源于stack exchange,提问作者user3166541




