Skip to content

148. cannon 实战:打保龄球(四)

Published:

上节实现了鼠标拖动来发球:

2025-11-16 22.13.55.gif

基于鼠标拖动的距离、方向来确定保龄球发球的方向、力度。

但发球的过程并没有可视化的展示,不知道现在力度有多大。

我们加一下可视化的展示。

方式很简单,就是根据拖动的方向、距离绘制一条线就行。

基于 2 个顶点画一条线,用 BufferGeomety + Line 就行。

image.png

let dragLine = null;
function createDragLine() {
    const lineGeometry = new THREE.BufferGeometry();
    const lineMaterial = new THREE.LineBasicMaterial({ 
        color: 0xffff00,
        transparent: true
    });
    dragLine = new THREE.Line(lineGeometry, lineMaterial);
    dragLine.visible = false;
    group.add(dragLine);
}

createDragLine();

刚开始是隐藏的,也没指定顶点。

拖拽的时候把它显示出来,并更新下顶点:

image.png

if (dragLine) dragLine.visible = true;
updateDragLine();
if (dragLine) dragLine.visible = false;

鼠标按下,显示辅助线。

鼠标拖动,更新辅助线位置、方向。

鼠标抬起,隐藏辅助线。

然后实现下这个 updateDragLine 方法:

image.png

function updateDragLine() {
    if (!dragLine || !ball.mesh) return;
    
    const dragVector = new THREE.Vector2().subVectors(dragEnd, dragStart);
    const dragDistance = dragVector.length();  // 拖拽距离
    
    // 计算力度:距离越大力度越大,最大 2000
    const force = Math.min(dragDistance * 500, 2000);
    
    // 线的起点是球的位置
    const startPos = ball.mesh.position.clone();
    
    // 计算发射方向:
    const forwardComponent = Math.max(0.1, -dragVector.y);  // 确保有向前的力
    const sideComponent = dragVector.x;  // 左右方向
    
    // 计算3D方向向量(在XZ平面上)
    // 系数1.5和1.2用于调整方向控制的灵敏度
    const direction = new THREE.Vector3(
        sideComponent * 1.5,      // X方向(左右)
        0,                         // Y方向(不向上发射)
        -forwardComponent * 1.2 - 0.5  // Z方向(向前,负值)
    ).normalize();
    
    // 线的长度与力度成正比
    const lineLength = force * 0.15;
    const endPos = startPos.clone().add(direction.multiplyScalar(lineLength));
    
    // 更新线的几何体顶点位置
    const positions = new Float32Array([
        startPos.x, startPos.y, startPos.z,
        endPos.x, endPos.y, endPos.z
    ]);
    dragLine.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    dragLine.geometry.attributes.position.needsUpdate = true;
}

其实和发球的计算逻辑差不多。

首先用两个位置的差,算出拖动的距离。

根据距离计算力度。

根据拖动向量的方向计算绘制线的方向。

基于力和方向来绘制线。

把两个顶点位置设置到 BufferGeometry,然后设置下 needsUpdate 提示更新了顶点位置就可以了。

试下效果:

2025-11-17 22.35.22.gif

这样,就可以可视化的看到拖动的距离了,直观感受到方向、力度。

因为我们是用圆柱作为保龄球瓶的物理刚体,和它真实的形状有点差距,所以效果有时候不不太好:

2025-11-17 22.17.10.gif

在真实项目里,可以用 blender 之类的软件画一些顶点,然后代码里基于这些顶点生成自定义形状的凸多面体就好了。

案例代码上传了小册仓库

总结

这节我们添加了发球的辅助线。

基于 BufferGeometry + Line 来绘制线,只要确定两个顶点位置就行。

一个顶点位置是球的位置,另一个根据鼠标拖拽来确定。

根据开始、结束位置的向量,计算出辅助线的方向、长度,这样就确定了另一个顶点位置,然后把线画出来。

这样,发球过程就更直观了。

评论