Skip to content

147. cannon 实战:打保龄球(三)

Published:

上节实现了打保龄球的基本效果:

2025-11-16 21.43.40.gif

这节我们自己控制保龄球的力度、方向的功能。

首先,要加鼠标事件肯定会和 OrbitControls 冲突,先把那个禁用。

image.png

然后把初始那个推力去掉:

image.png

2025-11-16 21.51.36.gif

这样,我们就需要自己拖动控制保龄球的方向、推力了。

加一下事件处理:

image.png

let isDragging = false;
let dragStart = new THREE.Vector2();
let dragEnd = new THREE.Vector2();
let mouse = new THREE.Vector2();  
function setupMouseInteraction() {
  const canvas = document.querySelector('canvas');

  canvas.addEventListener('mousedown', (event) => {
      if (ball.body.position.z > 100) {
          isDragging = true;
          event.preventDefault();
          
          mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
          mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
          
          dragStart.set(mouse.x, mouse.y);
          dragEnd.set(mouse.x, mouse.y);   
      }
  });

  canvas.addEventListener('mousemove', (event) => {
      if (isDragging) {
          event.preventDefault();
          
          mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
          mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
          dragEnd.set(mouse.x, mouse.y);          
      }
  });

  canvas.addEventListener('mouseup', (event) => {
      if (isDragging) {
          event.preventDefault();
          isDragging = false;

          shootBall();
      }
  });
}

setTimeout(() => {
    setupMouseInteraction();
}, 0);

function shootBall() {
  console.log('shootBall', dragEnd, dragStart);
  return;
}

监听鼠标按下、移动、抬起事件,记录开始、结束的鼠标位置。

然后抬起的时候调用 shootBall 来发球。

这里 setTimeout 也就是等 canvas 挂载后再监听事件。

然后只有在 z 大于 100 的区域内才可以发球。

2025-11-16 22.09.01.gif

这样,我们就可以知道拖动的方向、拖动的距离。

基于这个就可以算出发球的方向、力度。

实现下:

function shootBall() {
  console.log('shootBall', dragEnd, dragStart);

   const dragVector = new THREE.Vector2().subVectors(dragEnd, dragStart);
   const dragDistance = dragVector.length();
   
   // 如果拖拽距离太小,不发射(防止误触)
   if (dragDistance < 0.01) return;
   
   // 计算力度:拖拽距离 × 500,最大 2000
   // 这个系数决定了力度的大小,可以根据需要调整
   const force = Math.min(dragDistance * 500, 2000);
   
   // 计算发射方向
   const forwardComponent = Math.max(0.1, -dragVector.y);  // 向下拖拽 = 向前发射
   const sideComponent = dragVector.x;  // 左右拖拽 = 左右方向
   
   const direction = new CANNON.Vec3(
       sideComponent * 1.5,      // 左右方向系数
       0,                         // 不向上
       -forwardComponent * 1.2 - 0.5  // 向前方向系数
   );
   direction.normalize();  // 归一化,使方向向量长度为1
   
   // 重置球的速度和角速度(确保每次发射都是干净的状态)
   ball.body.velocity.set(0, 0, 0);
   ball.body.angularVelocity.set(0, 0, 0);
   
   // 应用冲量:方向 × 力度
   // applyImpulse 会在球的位置施加一个瞬间的力,使球开始运动
   ball.body.applyImpulse(direction.scale(force), ball.body.position);
  return;
}

这里用两个向量相减就算出了拖动的距离,拖动的距离乘以一个系数就是力度,当然最大力度要限制下。

之后方向,就是左右、以及向前的方向,归一化之后的方向向量。

确定了方向、力度,就可以发球了。

试下效果:

2025-11-16 22.13.55.gif

可以看到,现在就可以自己控制方向、力度了。

这样,我们打保龄球的基本功能就完成了。

案例代码上传了小册仓库

总结

这节我们实现了自己控制保龄球的方向、力度的功能。

原理就是监听鼠标事件,记录拖动开始和结束的坐标。

然后基于这两个坐标算出距离、方向,然后就可以设置推力的方向和力度了。

这样,就可以实现自己控制发球的效果。

评论