3、刚体与形状:理解刚体概念,Box、Sphere、Plane等基础碰撞体的创建与配置

好,咱们进入第三章。这一章要聊的是物理引擎里最核心的概念之一——刚体

刚体这个词,听起来挺唬人的。说白了,就是「不会变形的物体」。你想想看,现实中的石头、铁块、桌子腿,你用力砸它,它不会像橡皮泥一样凹进去。在物理引擎里,我们假设所有物体都是绝对坚硬的,碰撞瞬间不产生形变。当然,现实世界没有绝对的刚体,但游戏和仿真里,这个假设足够用了。

我个人习惯把刚体拆成两个部分来理解:运动属性碰撞形状。运动属性管它怎么飞、怎么转、受不受重力;碰撞形状管它跟别的物体怎么接触、怎么弹开。两者缺一不可。

3.1 刚体的核心属性

在Three.js里集成物理引擎(比如Ammo.js或Cannon-es),你创建刚体时,有几个参数是必须搞清楚的:

属性 说明 我的经验
mass 质量,单位kg 设为0就是静态物体,动不了
restitution 弹性系数,0~1 0.3左右比较真实,1.0会弹到停不下来
friction 摩擦力,0~1 0.3~0.5适合大多数地面
linearDamping 线性阻尼 模拟空气阻力,0.01就够了
angularDamping 角阻尼 让旋转慢慢停下来

关键点:质量设为0的物体,物理引擎会把它当作「静态刚体」。它不会受重力影响,也不会被其他物体推动。地面、墙壁、固定的障碍物,都应该用质量0。

3.2 基础碰撞体:Box、Sphere、Plane

碰撞体,就是物理引擎用来计算碰撞的「简化模型」。你想想看,如果每次碰撞都要拿真实的三角形网格去算,性能会爆炸。所以引擎用简单的几何体来近似。

我最早做项目时犯过一个错:把一个复杂的雕像模型直接扔给物理引擎,结果帧率掉到个位数。后来才明白,用几个Box和Sphere组合成碰撞体,效果差不多,性能却好十倍。

3.2.1 Box(盒子碰撞体)

最常用的碰撞体。适合箱子、墙壁、柱子、车辆等方方正正的物体。

// Cannon-es 示例
const shape = new CANNON.Box(new CANNON.Vec3(1, 1, 1)); // 半边长
const body = new CANNON.Body({ mass: 5 });
body.addShape(shape);
body.position.set(0, 5, 0);
world.addBody(body);

提示:Cannon-es的Box构造参数是半边长。如果你的Three.js盒子尺寸是2x2x2,那半边长就是1x1x1。这个对应关系搞反了,物体会「穿模」或者悬空。我刚开始就栽过这个跟头。

3.2.2 Sphere(球体碰撞体)

球体碰撞计算最简单,性能最好。适合球类、弹珠、圆形装饰物。

const shape = new CANNON.Sphere(1); // 半径
const body = new CANNON.Body({ mass: 2 });
body.addShape(shape);
body.position.set(-2, 3, 0);
world.addBody(body);

球体的好处是:不管怎么旋转,碰撞形状都不变。所以它永远不会「卡住」。我在做保龄球游戏时,所有球瓶都用Sphere做碰撞体,虽然看起来是瓶子形状,但物理表现足够真实。

3.2.3 Plane(平面碰撞体)

无限大的平面。通常用来做地面、墙壁、天花板。

const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({ mass: 0 }); // 静态
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(
  new CANNON.Vec3(1, 0, 0), -Math.PI / 2
); // 旋转使其朝上
world.addBody(groundBody);

注意:Plane默认的法线方向是(0, 0, 1),也就是朝向Z轴正方向。如果你想要一个水平地面,必须旋转它。我见过有人忘了旋转,结果物体「掉」到地底下去了——嗯,物理引擎可不会帮你纠正这个。

3.3 碰撞体与视觉模型的同步

刚体在物理世界里运动,但用户看到的是Three.js的Mesh。所以每一帧你都得把刚体的位置和旋转同步给Mesh。

// 在动画循环里
function update() {
  world.step(1 / 60);
  
  // 同步:物理体 -> 视觉体
  mesh.position.copy(body.position);
  mesh.quaternion.copy(body.quaternion);
  
  renderer.render(scene, camera);
  requestAnimationFrame(update);
}

这里有个坑:物理引擎的坐标轴和Three.js的坐标轴可能不一致。Cannon-es和Three.js都是Y轴向上,所以直接复制就行。但如果你用Ammo.js,它默认是Z轴向上,需要做转换。

3.4 选择合适的碰撞体

不是所有物体都适合用Box或Sphere。我列个对照表,方便你快速决策:

视觉形状 推荐碰撞体 原因
立方体、箱子 Box 贴合度高,计算快
球、弹珠 Sphere 性能最优,永不卡住
圆柱、树干 Cylinder 比Box更贴合,比Mesh快
复杂雕像 多个Box/Sphere组合 性能与精度的平衡
地形、地面 Plane 或 Heightfield Plane无限大,Heightfield有起伏

我的建议:能用简单几何体就别用Mesh碰撞体。Mesh碰撞体虽然精确,但计算量是几何体的几十倍。除非你做的是台球游戏或者精密装配仿真,否则用Box和Sphere组合完全够用。

3.5 避坑指南

做物理集成时,有几个坑我反复踩过,今天一并告诉你:

  • 质量与尺寸不匹配:一个边长10米的巨大箱子,质量只有1kg,物理表现会很诡异。现实中大物体质量也大,尽量保持比例合理。
  • 碰撞体比视觉模型小:如果碰撞体比Mesh小一圈,物体会「悬空」或者「穿模」。调试时可以把碰撞体可视化,确保两者对齐。
  • 忘记设置阻尼:没有阻尼的物体会像在真空中一样,永远弹跳不停。加一点线性阻尼和角阻尼,效果会自然很多。
  • 物理步长与渲染帧率不一致:物理引擎用固定步长(比如1/60),但渲染帧率可能波动。用world.step(fixedTimeStep, deltaTime)来处理,避免物理跑得比渲染快。

我曾经做一个堆叠游戏,箱子总是莫名其妙地滑落。查了两天,发现是Box的半边长写成了全边长,碰撞体比视觉模型大了一倍。嗯,这种低级错误,犯过一次就再也不会忘了。

3.6 小结

这一章我们聊了刚体的本质、三种基础碰撞体的创建与配置,以及如何与Three.js视觉模型同步。记住几个关键点:

  • 质量0 = 静态物体
  • Box用半边长,Sphere用半径
  • Plane需要手动旋转法线方向
  • 每帧同步物理体到视觉体

下一章我们会深入更复杂的碰撞体——Cylinder、Cone、Compound形状,以及如何用它们搭建更真实的物理世界。到时候见。

练习建议:创建一个场景,放一个Plane地面,然后从空中掉落一个Box和一个Sphere。调整它们的质量、弹性、摩擦力,观察运动变化。这是理解刚体行为最好的入门实验。