跳转到内容

Box2D — Erin Catto C++ 2D 物理

待复核

Box2D 是一个经典 2D 刚体物理引擎。日常类比:它像一个“可复用的物理教材引擎”——你负责画角色和规则,它负责每一帧把力、速度、摩擦、碰撞变成可复现的结果。

它的入口常见是:

  1. 创建 b2World
  2. 定义物体刚体、形状和 fixture
  3. 每帧调用 Step
  4. 按新物理状态同步到渲染层

这条链条让开发者不用手写积分和碰撞求解。

2D 游戏里“看起来自然”不是渲染特效,而是物理参数和离散时间积分的一致性。

  • gravity 不是只写 9.8:单位、缩放比都要和像素坐标系一致。
  • 一个小角速度抖动会在 10 秒后放大成整屏抖动。
  • 物理 step 稳定性直接决定体验。
  • 碰撞过滤、传感器、关节约束写错时,玩家看到的是“穿墙”“抖动”“卡死”,很难靠美术补救。
  1. 世界与步进要稳定b2World::Step(dt, velocityIterations, positionIterations) 是核心入口。类比:像节拍器,每拍都按同样时间推进,乐队才不会越演越乱。

    • dt 太大:穿透增强,隧穿风险上升。
    • 迭代次数太低:抖动和堆积穿透明显。
    • 迭代过高:性能掉得快但数值更稳定。
  2. 形状和 fixture 决定“怎么撞”。Circle、Polygon 是最常用,复杂边界可用链条边和胶囊体。类比:同一个玩具人,穿棉衣和穿盔甲,撞到墙的反应不一样。

    • 碰撞形状和 fixture 密度决定质量与惯性。
    • 传感器 fixture 不参与物理响应,仅作触发。
    • filter bits 决定某两类物体是否允许碰撞。
  3. 关节和约束把自由物体连成机构。Revolute、Prismatic、Distance、Weld 等关节让结构从“自由落体”变成“可控机械”。类比:门铰链只允许绕一个轴转,抽屉滑轨只允许前后滑。

    • 锁链系统要控制长度和阻尼。
    • 速度太高时要加限位器,避免数值发散。
    • 关节误差要用测试场景回归,不要只凭肉眼调一次。

一个球体从高处下落,碰撞地面后反弹:

b2World world({0.0f, -10.0f});
world.Step(1.0f / 60.0f, 8, 3);

逐部分解释

  • b2World 保存所有刚体、碰撞和关节,是物理“场地”。
  • {0.0f, -10.0f} 是重力方向;如果像素比例不统一,掉落速度会显得怪。
  • Step 的三个参数分别是固定时间步、速度迭代、位置迭代;8, 3 常作为教学起点。

角色施加脉冲力推动箱体时,若不设合适质量比例会出现“角色被推飞”或“箱子不动”。

boxBody->ApplyLinearImpulseToCenter({20.0f, 0.0f}, true);

逐部分解释

  • ApplyLinearImpulseToCenter 像“突然推一下”,适合跳跃、撞击、推箱子。
  • 第二个参数 true 会唤醒正在 sleep 的 body,避免推了没反应。
  • 箱体密度、摩擦、角色质量要一起调;质量差 10 倍以上时很容易出现不自然反弹。

让多个斜面形成轨道,关键是每段坡度和摩擦匹配。

const float fixedDt = 1.0f / 60.0f;
accumulator += frameDt;
while (accumulator >= fixedDt) {
world.Step(fixedDt, 8, 3);
accumulator -= fixedDt;
}

逐部分解释

  • frameDt 是真实帧间隔,会抖;fixedDt 是物理世界的固定节拍。
  • 用 accumulator 把渲染帧率和物理步进解耦,轨道才不随机器快慢改变。
  • 高速滚球仍要开启 CCD 或缩小步长,否则会穿过薄平台。
  1. 像素和米坐标混用:同一世界里 PTM 比例不一致会导致对象“忽大忽小”。
  2. 固定步长被忽略:真实帧率波动下直接用 deltaSeconds 会导致不稳定。
  3. 过早回收 body:没先销毁 joint 再 destroy body 可能引发野指针。
  4. 错误设置 sleep threshold:无休眠对象持续参与计算。
  5. 接触监听器逻辑副作用:BeginContact 和 EndContact 里改状态前先排队。

适用:

  • 2D 物理小游戏、模拟器、教育演示
  • 对性能有控制要求的轻量跨平台项目
  • 需要自己扩展碰撞/约束行为的团队

不适用:

  • 3D 或大规模物理粒子系统(应用 Box2D 会吃力)
  • 无法接受固定步长更新的系统
  • 极端高精度刚体仿真(建议更专门引擎)
  • 最初定位是让游戏团队避开重复造轮子。
  • 后续社区围绕 Cocos / Unity 等生态形成大量封装。
  • 在“轻量 2D”与“可控 determinism”之间,它形成了长期标杆。
  • 同类里,很多人从 Box2D 过渡到 Chipmunk / Planck,通常是为了生态语言差异。
  1. 物理引擎要点在于“稳定模型参数”,不是更多特效。
  2. 碰撞和约束是两个系统:处理接触是物理,处理反应是你定义的游戏逻辑。
  3. 固定步进是体验可复现的前提。
  4. 2D 物理的复杂度多数发生在边界场景而不是基础 API。
  5. 有序的世界构建顺序比参数魔改更重要。
  • collision-detection —— 碰撞检测原理
  • game-loop —— 固定步进与时间管理
  • physics-materials —— 摩擦 / 恢复系数实践
  • rigid-body —— 刚体物理基础
  • ecs —— 当对象管理规模变大时的结构模式
  • bullet —— Bullet — C++ 经典 3D 物理引擎与 PyBullet 仿真工具
  • matter-js —— Matter.js — 2D 刚体世界里最轻的“物理白板”
  • planck —— planck.js — 纯 JS Box2D 生态
  • rapier —— Rapier — Rust 现代 2D/3D 物理引擎