Skip to content

167. 实战:躲避汽车(三)

Published:

上节实现了汽车的行驶和人的行走:

2025-06-03 17.40.29.gif

这节我们来做下碰撞检测。

碰撞检测我们学过两种方式:

一种是包围盒,可以通过不同对象的包围盒是否相交来判断。

另一种是物理引擎,碰撞的时候有相应的事件。

这个案例我们用包围盒 Box3 的方式来判断。

首先,我们把包围盒可视化出来:

image.png

const helper = new THREE.BoxHelper(manGltf.scene);
group.add(helper);
manGltf.scene.helper = helper;

创建 BoxHelper,把它挂到对象上。

car 的也是一样:

image.png

const helper = new THREE.BoxHelper(car);
group.add(helper);
car.helper = helper;

但车和人的位置是变化的,要同步更新包围盒:

image.png

调用 helper.update 方法,传入对象,就会更新包围盒。

人的也是一样:

image.png

onUpdate() {
  man.helper.update(man);
}

看下效果:

2025-06-03 20.40.29.gif

车的包围盒还行,人的包围盒有点过于宽了:

image.png

这样碰撞检测的时候不够准确,我们裁剪掉一部分:

image.png

const box3 = new THREE.Box3();
box3.setFromObject(manGltf.scene);

const helper = new THREE.Box3Helper(box3);
group.add(helper);
manGltf.scene.helper = helper;

首先我们换成 Box3Helper,自己创建包围盒 Box3 让它包裹目标对象,然后可视化出来。

image.png

效果一样,但现在我们就可以裁剪了。

image.png

box3.expandByVector(new THREE.Vector3(-50, 0, 0));

用 expandByVector 在 x 方向两侧都去掉 50

image.png

现在包围盒大小就差不多了。

去掉 helper 和 update 的调用,因为 Box3Helper 没 update 方法:

image.png

image.png

在渲染循环里判断下是否碰撞:

首先,我们拿到 man 的对象,并且计算下包围盒,这里包围盒也要裁剪一部分:

image.png

然后用它和每辆车的包围盒判断是否碰撞,用 intersectsBox

如果碰撞了,就设置 gameOver 为 true,然后停止更新车辆位置。

image.png

let man;
let gameOver = false;
function render() {
    if(!man) {
      man = scene.getObjectByName('man');
    }
    let manBox3;
    if(man) {
      manBox3 = new THREE.Box3();
      manBox3.setFromObject(man);
      manBox3.expandByVector(new THREE.Vector3(-50, 0, 0));
    }

    if(!gameOver) {
      cars.forEach((arr, index) => {
        arr.forEach(item => {
          item.position.z += item.speed;
          item.helper.update(item);

          if(man) {
            const carBox3 = new THREE.Box3();
            carBox3.setFromObject(item);

            const collision = manBox3.intersectsBox(carBox3);
            if(collision) {
              gameOver = true;
            }
          }
        });

        arr = arr.filter(item => {
          if(item.position.z > 500) {
            item.parent?.remove(item);
            return false;
          };
          return true;
        })
      });
    }

    renderer.render(scene, camera);
    requestAnimationFrame(render);

}

看下效果:

2025-06-03 21.15.57.gif

可以看到,检测到了碰撞。

碰撞的时候车确实不动了,但还在产生。

我们加个判断:

image.png

export function isGameOver() {
  return gameOver;
}

image.png

if(isGameOver()) {
    return clearInterval(timer);
}

gameOver 就停止定时器。

2025-06-03 21.20.26.gif

现在车辆就不会再产生了。

我们把 BoxHelper 去掉,试下整体效果:

image.png

image.png

2025-06-03 21.22.32.gif

碰撞的时候,人物的走动动画也应该暂停:

image.png

if(isGameOver()) {
    clipAction.paused = true;
}

并且不能再移动:

image.png

if(gameOver) {
    return;
}

2025-06-03 21.27.34.gif

最后,修复一个问题:

现在从侧面碰撞检测不到,因为包围盒裁剪有点问题:

image.png

如果人转身了,那就应该在 z 方向裁剪,否则是 x 方向。

if(man.rotation.y === 0) {
    manBox3.expandByVector(new THREE.Vector3(-50, 0, 0));
} else {
    manBox3.expandByVector(new THREE.Vector3(0, 0, -50));
}

案例代码上传了小册仓库

总结

这节我们实现了碰撞检测。

通过包围盒 Box3 的 intersectsBox 实现的。

人物的包围盒有点大,我们用 expandByVector 做了裁剪。

碰撞之后设置 gameOver 状态,把各种活动都暂停。

下节我们加上鲜血喷射的粒子效果。

评论