Three.js结合Pointer Lock与交互式立方体时getHex调用报错
问题:Three.js交互物体报错
Cannot read properties of undefined (reading 'getHex') 开发教学用Three.js场景,集成官方Pointer Lock Control实现步行导航,参考webgl_interactive_cubes实现物体交互。步行功能正常,但交互部分出现如下报错:
Uncaught TypeError: Cannot read properties of undefined (reading 'getHex') at render (three-fps.js:200:62) at animate (three-fps.js:182:3)
相关代码:
import * as THREE from "three"; import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls.js"; let canvas; let camera, scene, raycaster, renderer; let INTERSECTED; let controls; let moveForward = false; let moveBackward = false; let moveLeft = false; let moveRight = false; let canJump = false; let prevTime = performance.now(); const velocity = new THREE.Vector3(); const direction = new THREE.Vector3(); const pointer = new THREE.Vector2(); const radius = 100; init(); animate(); function init() { canvas = document.getElementById("canvas"); camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.y = 10; //controls controls = new PointerLockControls(camera, canvas); const onKeyDown = function (event) { switch (event.code) { case "ArrowUp": case "KeyW": moveForward = true; break; case "ArrowLeft": case "KeyA": moveLeft = true; break; case "ArrowDown": case "KeyS": moveBackward = true; break; case "ArrowRight": case "KeyD": moveRight = true; break; case "Space": if (canJump === true) velocity.y += 350; canJump = false; break; } }; const onKeyUp = function (event) { switch (event.code) { case "ArrowUp": case "KeyW": moveForward = false; break; case "ArrowLeft": case "KeyA": moveLeft = false; break; case "ArrowDown": case "KeyS": moveBackward = false; break; case "ArrowRight": case "KeyD": moveRight = false; break; } }; document.addEventListener("keydown", onKeyDown); document.addEventListener("keyup", onKeyUp); scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(1, 1, 1).normalize(); scene.add(light); const geometry = new THREE.BoxGeometry(20, 20, 20); for (let i = 0; i < 2000; i++) { const object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }) ); object.position.x = Math.random() * 800 - 400; object.position.y = Math.random() * 800 - 400; object.position.z = Math.random() * 800 - 400; object.rotation.x = Math.random() * 2 * Math.PI; object.rotation.y = Math.random() * 2 * Math.PI; object.rotation.z = Math.random() * 2 * Math.PI; object.scale.x = Math.random() + 0.5; object.scale.y = Math.random() + 0.5; object.scale.z = Math.random() + 0.5; scene.add(object); } raycaster = new THREE.Raycaster(); // floor let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100); floorGeometry.rotateX(-Math.PI / 2); const floorMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); const floor = new THREE.Mesh(floorGeometry, floorMaterial); scene.add(floor); // Create a WebGL renderer renderer = new THREE.WebGLRenderer({ canvas }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(canvas.clientWidth, canvas.clientHeight); document.addEventListener("mousemove", onPointerMove); // window.addEventListener("resize", onWindowResize); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function onPointerMove(event) { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; } // function animate() { requestAnimationFrame(animate); const time = performance.now(); const delta = (time - prevTime) / 1000; velocity.x -= velocity.x * 10.0 * delta; velocity.z -= velocity.z * 10.0 * delta; direction.z = Number(moveForward) - Number(moveBackward); direction.x = Number(moveRight) - Number(moveLeft); direction.normalize(); // this ensures consistent movements in all directions if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta; if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta; controls.moveRight(-velocity.x * delta); controls.moveForward(-velocity.z * delta); prevTime = time; camera.updateMatrixWorld(); render(); } function render() { camera.updateMatrixWorld(); // find intersections raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects(scene.children, false); if (intersects.length > 0) { if (INTERSECTED != intersects[0].object) { if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = intersects[0].object; INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); INTERSECTED.material.emissive.setHex(0xff0000); } } else { if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = null; } renderer.render(scene, camera); }
解决方案
报错核心原因:地板使用的MeshBasicMaterial不包含emissive属性,当射线检测到地板时,INTERSECTED.material.emissive会返回undefined,调用getHex()就触发了报错。
方法1:给地板改用支持emissive的材质
将地板的MeshBasicMaterial替换为MeshLambertMaterial(和立方体材质一致):
const floorMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
方法2:射线检测时排除地板
子方法2.1:将立方体分组,只检测组内物体
在init函数中创建立方体组,把所有立方体添加到组里,射线检测仅针对该组:
// 初始化时创建立方体组 const cubeGroup = new THREE.Group(); scene.add(cubeGroup); // 创建立方体时添加到cubeGroup而非scene cubeGroup.add(object); // render函数中修改检测目标 const intersects = raycaster.intersectObjects(cubeGroup.children, false);
子方法2.2:过滤场景中的地板对象
在射线检测时排除地板:
const intersects = raycaster.intersectObjects(scene.children.filter(child => child !== floor), false);
方法3:添加属性存在性判断(兜底方案)
在访问emissive前先判断是否存在,避免意外报错:
// 替换render函数中的相关逻辑 if (intersects.length > 0) { if (INTERSECTED != intersects[0].object) { if (INTERSECTED && INTERSECTED.material.emissive) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = intersects[0].object; if (INTERSECTED.material.emissive) { INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); INTERSECTED.material.emissive.setHex(0xff0000); } } } else { if (INTERSECTED && INTERSECTED.material.emissive) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = null; }
内容的提问来源于stack exchange,提问作者guyfromthesun
相关产品推荐
相关产品推荐

