Three.js中GLTF场景碰撞检测实现:阻止玩家穿墙
实现GLTF关卡的碰撞检测(阻止玩家穿墙)
我在游戏中导入了一个GLTF场景/关卡(Backrooms Level 0),希望实现碰撞检测以阻止玩家穿墙。我的加载器代码如下:
loader.load( // resource URL "../../models/scene.gltf", // called when the resource is loaded function (gltf) { scene.add(gltf.scene); level = gltf.scene; gltf.animations; // Array<THREE.AnimationClip> gltf.scene; // THREE.Group gltf.scenes; // Array<THREE.Group> gltf.cameras; // Array<THREE.Camera> gltf.asset; // Object }, // called while loading is progressing function (xhr) { console.log((xhr.loaded / xhr.total) * 100 + "% loaded"); }, // called when loading has errors function (error) { console.error(error); } return { level };
方法1:为关卡生成简易碰撞体(性能优先)
由于原GLTF模型面数通常较高,直接用原网格做碰撞会导致性能问题,建议为每个网格生成凸面体碰撞体:
function (gltf) { scene.add(gltf.scene); level = gltf.scene; // 遍历关卡网格,生成碰撞体 level.traverse((child) => { if (child.isMesh) { // 基于原网格顶点生成凸面体碰撞几何体 const convexGeometry = new THREE.ConvexGeometry(child.geometry.attributes.position.array); // 创建不可见的碰撞网格 const collider = new THREE.Mesh(convexGeometry); // 同步原网格的位置、旋转、缩放 collider.position.copy(child.position); collider.rotation.copy(child.rotation); collider.scale.copy(child.scale); // 标记为碰撞体,方便后续检测 collider.userData.isCollider = true; scene.add(collider); } }); gltf.animations; // Array<THREE.AnimationClip> gltf.scene; // THREE.Group gltf.scenes; // Array<THREE.Group> gltf.cameras; // Array<THREE.Camera> gltf.asset; // Object },
方法2:用Raycaster做射线碰撞检测(适合第一/第三人称玩家)
在玩家移动逻辑中,通过发射射线预判是否会穿墙:
// 假设player为玩家对象,moveDirection为移动方向向量,moveStep为单次移动距离 const raycaster = new THREE.Raycaster(); raycaster.set(player.position, moveDirection.normalize()); // 筛选所有标记为碰撞体的对象 const colliders = scene.children.filter(child => child.userData.isCollider); const intersects = raycaster.intersectObjects(colliders); // 若射线检测到碰撞且距离小于移动步长,阻止移动 if (intersects.length > 0 && intersects[0].distance < moveStep) { return; } else { player.position.add(moveDirection.multiplyScalar(moveStep)); }
方法3:使用物理引擎实现真实碰撞(如Cannon.js)
如果需要跳跃、碰撞反弹等复杂物理效果,推荐使用专业物理引擎:
- 引入Cannon.js库
- 加载关卡时创建对应物理体:
// 初始化Cannon物理世界 const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // 加载完成回调中添加物理体 function (gltf) { scene.add(gltf.scene); level = gltf.scene; level.traverse((child) => { if (child.isMesh) { // 基于原网格创建Cannon碰撞形状 const shape = new CANNON.ConvexPolyhedron(child.geometry.attributes.position.array); // 创建静态物理体(关卡不移动,质量设为0) const body = new CANNON.Body({ mass: 0, shape: shape, position: new CANNON.Vec3(child.position.x, child.position.y, child.position.z), quaternion: new CANNON.Quaternion(child.rotation.x, child.rotation.y, child.rotation.z, child.rotation.w) }); world.addBody(body); child.userData.physicsBody = body; } }); }, // 每一帧同步物理世界与Three.js场景 function animate() { requestAnimationFrame(animate); // 更新物理模拟 world.step(1/60); // 同步玩家对象与物理体的位置、旋转 player.position.copy(player.userData.physicsBody.position); player.quaternion.copy(player.userData.physicsBody.quaternion); renderer.render(scene, camera); }
内容的提问来源于stack exchange,提问作者user20637858
相关产品推荐
相关产品推荐

