原文参考我的公众号文章 # 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缓动动画,让镜头平滑些。