Skip to content

143. 物理引擎 cannon:小球发射(二)

Published:

上节把场景画了出来:

2025-11-16 16.47.45.gif

这节实现物理效果。

引入 cannon-es:

pnpm install --save cannon-es

创建物理世界,并且把平面、每个立方体、小球在物理世界里定义下:

首先创建物理世界:

image.png

import * as CANNON from 'cannon-es';

const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);

const groundMaterial = new CANNON.Material('ground');
const boxMaterial = new CANNON.Material('box');
const sphereMaterial = new CANNON.Material('sphere');
const contactBox = new CANNON.ContactMaterial(groundMaterial, boxMaterial, {
  friction: 0.4,    // 摩擦力:越大越难滑动
  restitution: 0.1  // 弹性:0为不反弹,1为完全弹性
});
world.addContactMaterial(contactBox);
const contactSphereGround = new CANNON.ContactMaterial(groundMaterial, sphereMaterial, {
  friction: 0.2,
  restitution: 0.1
});
world.addContactMaterial(contactSphereGround);
const contactSphereBox = new CANNON.ContactMaterial(sphereMaterial, boxMaterial, {
  friction: 0.3,
  restitution: 0.0
});
world.addContactMaterial(contactSphereBox);

我们创建了物理世界,定义了 y 方向的重力加速度。

然后创建了地面、盒子、球的材质,定义了两两的碰撞效果:

球和地面、球和盒子、盒子和地面的碰撞时的摩擦力、弹力。

这个可以看效果再调也行。

先加下地面的刚体:

image.png

const groundBody = new CANNON.Body({ mass: 0, material: groundMaterial });
groundBody.addShape(new CANNON.Plane());
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);

地面质量为 0,也就是不会移动。

然后加一下各种立方体的刚体:

image.png

const boxes = [];
const body = new CANNON.Body({
    mass: 1,
    material: boxMaterial,
    position: new CANNON.Vec3(x, y, z)
});
body.addShape(new CANNON.Box(new CANNON.Vec3(half, half, half)));
world.addBody(body);
boxes.push({ mesh, body });

给每个立方体加一个物理世界的刚体,这里注意宽高要按照一半设置。

最后,再给球加一个物理世界的刚体:

image.png

const balls = [];
const geometry = new THREE.SphereGeometry(20);
const material = new THREE.MeshStandardMaterial({ 
    color: 'blue', 
    metalness: 0.8, 
    roughness: 0.2
});
function shootBallAt(x, y, z) {
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(x, y + 20, z);
    group.add(mesh);
    const body = new CANNON.Body({
        mass: 10,
        material: sphereMaterial,
        position: new CANNON.Vec3(x, y + 20, z)
    });
    body.addShape(new CANNON.Sphere(20));
    world.addBody(body);
    balls.push({ mesh, body });

    const impulse = new CANNON.Vec3(0, 0, -5000);
    body.applyImpulse(impulse, body.position);
}

shootBallAt(0, 0, 0);

这里用到 body.applyImpulse 给物体加了一个 z 轴方向的推力。

最后,让物理世界的位置、角度变化同步到 Three.js 场景:

image.png

function render() {
    world.fixedStep();

    boxes.forEach(({ mesh, body }) => {
      mesh.position.copy(body.position);
      mesh.quaternion.copy(body.quaternion);
    });

    balls.forEach(({ mesh, body }) => {
      mesh.position.copy(body.position);
      mesh.quaternion.copy(body.quaternion);
    });

    requestAnimationFrame(render);
}
render();

2025-11-16 17.25.51.gif

这样,球初始收到一个推力,就会滚动起来,撞飞立方体。

案例代码上传了小册仓库

总结

这节我们实现了 cannon 物理世界,定义了立方体、平面、球在物理世界的刚体。

然后给球加了一个推力,让它滚动起来撞击立方体堆。

当然,现在的效果还不是很好,下节我们继续优化,并且加上点击发射小球的功能。

评论