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

这节我们自己控制保龄球的力度、方向的功能。
首先,要加鼠标事件肯定会和 OrbitControls 冲突,先把那个禁用。

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


这样,我们就需要自己拖动控制保龄球的方向、推力了。
加一下事件处理:

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 的区域内才可以发球。

这样,我们就可以知道拖动的方向、拖动的距离。
基于这个就可以算出发球的方向、力度。
实现下:
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;
}
这里用两个向量相减就算出了拖动的距离,拖动的距离乘以一个系数就是力度,当然最大力度要限制下。
之后方向,就是左右、以及向前的方向,归一化之后的方向向量。
确定了方向、力度,就可以发球了。
试下效果:

可以看到,现在就可以自己控制方向、力度了。
这样,我们打保龄球的基本功能就完成了。
案例代码上传了小册仓库
总结
这节我们实现了自己控制保龄球的方向、力度的功能。
原理就是监听鼠标事件,记录拖动开始和结束的坐标。
然后基于这两个坐标算出距离、方向,然后就可以设置推力的方向和力度了。
这样,就可以实现自己控制发球的效果。