You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:40:55