使用THREE.Raycaster点击网格始终返回ID8的问题求助
问题原因:闭包导致的变量绑定陷阱
你遇到的问题是典型的JavaScript闭包特性带来的坑——在循环中给每个网格的callback赋值时,你引用的是同一个idNumberForTest变量,而非循环执行到当前步骤时的数值。当整个循环结束后,idNumberForTest已经累加到了8(4行×2列刚好8个网格),所以不管点击哪个网格,回调函数触发时都会读取这个最终值。
解决方案1:使用let创建块级作用域(推荐)
在循环内部用let声明一个局部变量,把当前的ID值存进去。这样每次循环都会生成一个独立的作用域,回调函数会绑定到当前循环的ID值,而非共享的全局变量:
for(i=1; i<=countInColumns; i++ ) { var geometry = new THREE.BoxGeometry(maxTileSizeWidth, maxTileSizeHeight, 1 ); var tileX = new THREE.Mesh( geometry, materialsTiles ); tileX.position.set(actualCoordinatesX,actualCoordinatesY,0); actualCoordinatesX = actualCoordinatesX + maxTileSizeWidth + distanceBetween; idNumberForTest=idNumberForTest + 1; // 用let保存当前循环的ID,避免闭包共享变量 let currentTileId = idNumberForTest; tileX.callback = function(){callFromTitleWithId(currentTileId+'')}; arrayTiles.push(tileX); sceneWorld.add( tileX ); }
解决方案2:使用立即执行函数(IIFE)兼容旧环境
如果你的运行环境不支持ES6的let,可以用立即执行函数创建独立作用域来捕获当前ID:
for(i=1; i<=countInColumns; i++ ) { var geometry = new THREE.BoxGeometry(maxTileSizeWidth, maxTileSizeHeight, 1 ); var tileX = new THREE.Mesh( geometry, materialsTiles ); tileX.position.set(actualCoordinatesX,actualCoordinatesY,0); actualCoordinatesX = actualCoordinatesX + maxTileSizeWidth + distanceBetween; idNumberForTest=idNumberForTest + 1; // 用IIFE把当前ID作为参数传入,形成独立作用域 (function(tileId){ tileX.callback = function(){callFromTitleWithId(tileId+'')}; })(idNumberForTest); arrayTiles.push(tileX); sceneWorld.add( tileX ); }
解决方案3:直接将ID绑定到Mesh对象(更直观)
Three.js推荐用userData属性存储自定义数据,我们可以把ID直接存在网格对象上,回调时读取这个属性:
for(i=1; i<=countInColumns; i++ ) { var geometry = new THREE.BoxGeometry(maxTileSizeWidth, maxTileSizeHeight, 1 ); var tileX = new THREE.Mesh( geometry, materialsTiles ); tileX.position.set(actualCoordinatesX,actualCoordinatesY,0); actualCoordinatesX = actualCoordinatesX + maxTileSizeWidth + distanceBetween; idNumberForTest=idNumberForTest + 1; // 用Three.js官方推荐的userData存储自定义ID tileX.userData.tileId = idNumberForTest; tileX.callback = function(){callFromTitleWithId(this.userData.tileId+'')}; arrayTiles.push(tileX); sceneWorld.add( tileX ); }
额外小提示
- 你的
idNumberForTest初始值是0,第一个网格的ID会是1,如果需要从0开始可以调整初始值 - 使用
userData存储自定义数据可以避免和Three.js内置属性冲突,是更规范的写法
内容的提问来源于stack exchange,提问作者Matthias
相关产品推荐
相关产品推荐

