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

Three.js组内对象射线检测触发多动画问题求助

解决Three.js Raycaster点击触发多个对象动画的问题

嘿,我看你遇到了点击被遮挡对象时,Raycaster同时检测到多个对象导致动画串扰的问题。这其实是因为当前的检测逻辑没有考虑对象的距离优先级——射线会穿过前面的对象,检测到所有相交的物体,而你现在是分别判断每个对象是否被射线命中,不管它们离相机的远近,所以就会同时触发多个动画。咱们来一步步修复这个问题:

问题根源分析

你当前的点击事件里,分别调用了raycaster.intersectObject(fnup)raycaster.intersectObject(old)等方法,只要某对象和射线相交,就会执行对应的动画。但当目标被其他对象遮挡时,射线会同时命中遮挡物和目标物,两个条件都满足,自然就会同时触发两组动画。

raycaster.intersectObjects(group.children)返回的数组是按距离相机由近到远排序的,第一个元素就是用户实际点击到的最上层对象——这才是我们需要处理的目标。

解决方案:只处理最近的相交对象

我们可以重构点击事件的逻辑,优先取intersections数组的第一个元素,然后根据这个对象的标识(比如userData里的属性)来执行对应的动画,这样就不会触发被遮挡对象的动画了。

优化后的代码示例

首先,确保你给每个对象的userData添加了唯一标识(比如在创建对象时):

// 示例:给各个对象添加标识
fnup.userData.key = 'fnup';
old.userData.key = 'old';
cam.userData.key = 'cam';
alex.userData.key = 'alex';

然后修改onDocumentMouseDown函数:

function onDocumentMouseDown(event) {
    event.preventDefault();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);
    
    const intersections = raycaster.intersectObjects(group.children);
    // 只处理最近的那个相交对象
    if (intersections.length > 0) {
        const clickedObj = intersections[0].object;
        const objKey = clickedObj.userData.key;
        
        // 封装统一隐藏逻辑,避免重复代码
        function hideAllObjects() {
            TweenMax.to(fnup, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut});
            TweenMax.to(old, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut});
            TweenMax.to(alex, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut});
            TweenMax.to(cam, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut});
            TweenMax.to(mirrorCube, 1, {three:{y:-400, opacity: 0 }, ease:Power2.easeInOut});
            oldRotate = false;
            groupRotate = false;
            fnupPosition = false;
        }
        
        hideAllObjects();
        
        // 根据对象标识执行对应的放大动画
        switch(objKey) {
            case 'old':
                TweenMax.to(old, 1.5, {three:{scaleX: 2.5, scaleY: 2.5, x:0, y:0, z:0, rotationX: 0 }, ease:Power2.easeInOut});
                break;
            case 'fnup':
                TweenMax.to(fnup, 1.5, {three:{scaleX: 2.5, scaleY: 2.5, x:0, y:0, z:0 }, ease:Power2.easeInOut});
                break;
            case 'cam':
                TweenMax.to(cam, 1.5, {three:{scaleX: 2.5, scaleY: 2.5, x:0, y:0, z:0 , rotationX: 0}, ease:Power2.easeInOut});
                break;
            case 'alex':
                TweenMax.to(alex, 1.5, {three:{scaleX: 2.5, scaleY: 2.5, x:0, y:0, z:0, rotationX: 0 }, ease:Power2.easeInOut});
                break;
        }
    }
};

额外优化建议

  • 防止重复触发动画:可以在执行动画前判断对象是否正在被动画,比如用TweenMax.isTweening(old)来检查,避免重复点击导致的动画混乱。
  • 统一管理对象:把fnupold这些对象放到一个数组里,遍历执行隐藏动画,进一步减少重复代码。
  • 射线检测优化:如果你的group里有很多子对象,可以考虑给不需要检测的对象设置raycastable = false,减少射线检测的计算量。

这样修改后,点击时只会触发最上层(离相机最近)的对象动画,就不会出现被遮挡对象也触发动画的问题啦~

内容的提问来源于stack exchange,提问作者Jacob Truax

火山引擎 最新活动