关于three.js:threejs碰撞检测前进后退上下楼梯一口气搞定

6次阅读

共计 3265 个字符,预计需要花费 9 分钟才能阅读完成。

原文参考我的公众号文章 # threejs 碰撞检测 - 后退后退,高低楼梯一口气搞定!

物体挪动 – 前后碰撞检测

次要还是依附 Raycaster(origin, direction) 射线检测。在物体前后挪动时,实时获取物体的地位 origin=target.position.clone() 作为「射线发射点」,并通过 player.getWorldDirection(dir) 实时获取物体的方向作为射线「发射方向」,当物体挪动的 forward>0,示意后退,否则示意后退。如果是后退,则 dir.negate(); 方向反转。

效果图

GIF 图只有 4fps,20 品质,所以很卡。否则图片太大了

次要代码,正文具体阐明了过程

/**
 * JoyStick 控制器 - 角色碰撞检测【前|后】* @param {Object3D} target 须要碰撞检测的挪动物体
 * @param {THREE.Raycaster} raycaster 挪动物体的碰撞检测射线
 * @param {Number} intersectDistance 检测间隔,小于此间隔示意碰撞了
 * @param {Boolean} recursive 是否递归检测场景中的物体
 * @returns Boolean
 */
function checkCollide(
  target,
  raycaster,
  intersectDistance = 2,
  recursive = true
) {
  // 是否在【后退】let isForward = this.js.forward >= 0;

  // 获取 target 以后地位,Y 轴加一个固定量,代表纵轴射线发射(检测碰撞的)地位
  let origin = target.position.clone().add(new THREE.Vector3(0, 1, 0));

  // 获取 target 以后朝向
  let direction = new THREE.Vector3(); // 定义一个方向向量
  this.player.getWorldDirection(direction);
  direction.normalize();

  this.playerLight.position.x = this.player.position.x;
  this.playerLight.position.y = this.player.position.y + 2;
  this.playerLight.position.z = this.player.position.z;

  // 如果在【后退】,检测方向 direction 取反
  if (!isForward) {direction.negate();
    // console.log("move fallback:", direction);
  } else {// console.log("move forward:", direction);
  }

  // 设置射线发射地位
  raycaster.ray.origin.copy(origin);

  // 设置射线发射方向
  raycaster.ray.direction.copy(direction);

  // 开始【前、后】检测:对于 blender 制作的模型,须要递归遍历所有 child,否则无奈实现射线碰撞检测{[childs], true}
  let ins = raycaster.intersectObjects(this.objects, recursive);
  if (ins.length) {let { distance, object} = ins[0];
    if (object.name == "stair") {return false;}
    if (distance < intersectDistance) {console.warn("💥碰撞了:", object);

      // 主动回退 1 米,给接下来小角度后退留余地,否则会继续触发碰撞条件而无奈挪动
      if (isForward) {console.log("💥后退过程碰撞了,主动后退 1 米");
        this.player.translateZ(-1);
      } else {console.log("💥后退过程碰撞了,主动后退 1 米");
        this.player.translateZ(+1);
      }
      return true;
    }
  }
  return false;
}

一些优化

  • 产生碰撞时,依据此前挪动方向,向相同的方向回退 1m(this.player.translateZ(±1);),这样能够避免转弯时继续碰撞物体
  • TODO 还未解决在楼梯下后退后退对楼梯的碰撞检测

物体挪动 – 高低楼梯

次要还是依附 THREE.Raycaster(origin, dir) 射线检测。从挪动物体的顶部的中央收回射线,方向朝下,检测楼梯,进行间隔差值判断。须要留神的点在于要躲避人「走在楼梯下」的场景,不要误判为「上楼」。

效果图

GIF 图只有 4fps,20 品质,所以很卡。否则图片太大了

高低楼梯射线碰撞检测代码

/**
 * target 检测的挪动指标
 */
function checkStairCollide(target) {// 获取 target 以后地位,Y 轴加一个固定量(50 米),代表纵轴射线发射(检测碰撞的)地位
  let origin = target.position.clone().add(new THREE.Vector3(0, 50, 0));

  // 获取 target 以后朝向
  let direction = new THREE.Vector3(0, -1, 0); // 定义一个向下的方向向量

  let raycaster = new THREE.Raycaster(origin, direction);

  let ins = raycaster.intersectObjects(this.objects, true);

  if (ins.length) {let { distance, object} = ins[0];

    const name = object.name;
    const oneStairHeight = 0.4; // 一个楼梯高度
    const fallenSpeed = 8; // 脱离楼梯或高台后的坠落速度
    let diffY = origin.y - distance;
    let targetY = target.position.y;

    // 脱离楼梯或高台,开始坠落
    if (name.includes("floor") || name.includes("ground")) {
      // 坠落动画
      let fallDistance = target.position.y;
      gsap.to(target.position, {
        y: 0,
        duration: fallDistance / fallenSpeed,
      });
      return;
    }

    // 遇到楼梯,上楼检测
    if (name.includes("stair")) {
      let diffHeigh = diffY - targetY;
      // 超过 2 个楼梯高度不进行上楼检测,阐明在楼梯下走路
      if (diffHeigh >= oneStairHeight * 2) {return;}

      // 射线命中的地位比 target 的以后 Y 轴地位高,能够上楼
      if (diffY > targetY) {
        // target.position.y = diffY;

        // 动画过渡一下
        gsap.to(target.position, {
          y: diffY,
          ease: "linear",
          duration: 0.1, // 这个提早如果太久,会导致 target.position.y 更新不及时而无奈判断为上楼梯
        });
        console.log("上楼梯⬆️");
      } else if (diffY < targetY) {
        target.position.y -= 0.1;
        console.log("下楼梯⬇️");
      }
    }
  }
}

一些优化

  • 检测在楼梯下静止:
const oneStairHeight = 0.4; // 一个楼梯高度

if (name.includes("stair")) {
  let diffHeigh = diffY - targetY;
  // 超过 2 个楼梯高度不进行上楼检测,阐明在楼梯下走路
  if (diffHeigh >= oneStairHeight * 2) {return;}

  //   ... 高低楼梯
}
  • 退出 gsap 缓动动画,让镜头平滑些。
正文完
 0