在THREE.js中实现玩家碰撞墙体后的移动限制最优方案
游戏碰撞检测与障碍物阻挡移动解决方案
我正在开发一款带重力系统的游戏,需要实现玩家碰到墙体等障碍物后无法移动的功能。之前试过只停止向前移动,但玩家转向后能反向穿墙,至今没找到可行方案。以下是我的移动代码:
document.onkeydown = function(e) { // e.preventDefault() if(e.keyCode == 87) { player.movement.w = true } if(e.keyCode == 65) { player.movement.a = true } if(e.keyCode == 83) { player.movement.s = true } if(e.keyCode == 68) { player.movement.d = true } if(e.keyCode == 69) { player.movement.e = true } if(e.keyCode == 81) { player.movement.q = true } } document.onkeyup = function(e) { if(e.keyCode == 87) { player.movement.w = false } if(e.keyCode == 65) { player.movement.a = false } if(e.keyCode == 83) { player.movement.s = false } if(e.keyCode == 68) { player.movement.d = false } if(e.keyCode == 69) { player.movement.e = false } if(e.keyCode == 81) { player.movement.q = false } } function loop() { requestAnimationFrame(loop) if(player.look.locked == true) { if(player.movement.w == true) { playerObj.translateZ(player.movement.speed / 100) } if(player.movement.a == true) { playerObj.translateX(player.movement.speed / 100) } if(player.movement.s == true) { playerObj.translateZ(player.movement.speed / 100 * -1) } if(player.movement.d == true) { playerObj.translateX(player.movement.speed / 100 * -1) } if(player.movement.e == true) { playerObj.translateY(player.movement.speed / 100 * -1) } if(player.movement.q == true) { playerObj.translateY(player.movement.speed / 50) } } } loop()
(我知道event.keyCode已被废弃)
核心解决思路:预判移动,分轴碰撞检测
当前代码直接修改玩家位置,没有碰撞校验,导致穿墙问题。正确的做法是先计算移动后的目标位置,检测是否与障碍物碰撞,仅在无碰撞时执行移动,且分轴检测能让玩家在非碰撞方向正常移动(比如贴墙左右走)。
1. 实现3D轴对齐碰撞检测(AABB)
先写一个通用的3D碰撞检测函数,判断两个矩形体是否重叠:
// 3D AABB碰撞检测:a和b需包含left/right、top/bottom、front/back边界值 function check3DCollision(a, b) { return !( a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom || a.back < b.front || a.front > b.back ); }
2. 修改移动逻辑,加入碰撞预判
将直接位移改为先计算增量→预判目标位置→检测碰撞→执行移动,同时分轴处理提升体验:
function loop() { requestAnimationFrame(loop); if(player.look.locked) { // 计算各方向移动增量 const speed = player.movement.speed / 100; let moveX = 0, moveY = 0, moveZ = 0; if(player.movement.w) moveZ += speed; if(player.movement.a) moveX += speed; if(player.movement.s) moveZ -= speed; if(player.movement.d) moveX -= speed; if(player.movement.e) moveY -= speed; if(player.movement.q) moveY += speed * 2; // Q键速度是2倍 // 辅助函数:获取玩家当前的边界信息 function getPlayerBounds() { const pos = playerObj.position; // 假设playerObj有{x,y,z}格式的位置属性 const halfW = player.width / 2; const halfH = player.height / 2; const halfD = player.depth / 2; return { left: pos.x - halfW, right: pos.x + halfW, top: pos.y - halfH, bottom: pos.y + halfH, front: pos.z - halfD, back: pos.z + halfD }; } // 辅助函数:获取单个障碍物的边界信息 function getObstacleBounds(obstacle) { const pos = obstacle.position; const halfW = obstacle.width / 2; const halfH = obstacle.height / 2; const halfD = obstacle.depth / 2; return { left: pos.x - halfW, right: pos.x + halfW, top: pos.y - halfH, bottom: pos.y + halfH, front: pos.z - halfD, back: pos.z + halfD }; } // 分轴检测并执行移动 // X轴检测 const currentBounds = getPlayerBounds(); const targetXBounds = { ...currentBounds, left: currentBounds.left + moveX, right: currentBounds.right + moveX }; let canMoveX = true; for(const obs of obstacles) { if(check3DCollision(targetXBounds, getObstacleBounds(obs))) { canMoveX = false; break; } } if(canMoveX) playerObj.translateX(moveX); // Y轴检测(需结合重力逻辑,比如落地时重置地面状态) const newCurrentBounds = getPlayerBounds(); const targetYBounds = { ...newCurrentBounds, top: newCurrentBounds.top + moveY, bottom: newCurrentBounds.bottom + moveY }; let canMoveY = true; for(const obs of obstacles) { if(check3DCollision(targetYBounds, getObstacleBounds(obs))) { canMoveY = false; // 若向下移动时碰撞,标记玩家在地面(用于重力系统) if(moveY < 0) player.isGrounded = true; break; } } if(canMoveY) { playerObj.translateY(moveY); player.isGrounded = false; // 离开地面时取消标记 } // Z轴检测 const finalCurrentBounds = getPlayerBounds(); const targetZBounds = { ...finalCurrentBounds, front: finalCurrentBounds.front + moveZ, back: finalCurrentBounds.back + moveZ }; let canMoveZ = true; for(const obs of obstacles) { if(check3DCollision(targetZBounds, getObstacleBounds(obs))) { canMoveZ = false; break; } } if(canMoveZ) playerObj.translateZ(moveZ); } }
3. 替换废弃的keyCode为event.key
用更直观、兼容更好的event.key替代keyCode:
document.onkeydown = (e) => { switch(e.key.toLowerCase()) { case 'w': player.movement.w = true; break; case 'a': player.movement.a = true; break; case 's': player.movement.s = true; break; case 'd': player.movement.d = true; break; case 'e': player.movement.e = true; break; case 'q': player.movement.q = true; break; } }; document.onkeyup = (e) => { switch(e.key.toLowerCase()) { case 'w': player.movement.w = false; break; case 'a': player.movement.a = false; break; case 's': player.movement.s = false; break; case 'd': player.movement.d = false; break; case 'e': player.movement.e = false; break; case 'q': player.movement.q = false; break; } };
内容的提问来源于stack exchange,提问作者Owen Odroski
相关产品推荐
相关产品推荐

