You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动