Godot 的 2D 和 3D 物理是两套独立实现(servers/physics_2d/ 与 servers/physics_3d/),2D 侧没有 Jolt 这样的第三方替代品——Jolt 只做 3D。所以 2D 游戏的物理表现完全取决于 Godot 自研这套,了解它的设计取舍比 3D 侧更实用。结论先说:2D 求解比 3D 简单主要简单在两个地方,窄相(SAT 替代 GJK/MPR)和惯量(标量替代张量)。
SAT:凸多边形碰撞的完整判据
Godot 2D 的凸形状碰撞走分离轴定理(Separating Axis Theorem, servers/physics_2d/collision_solver_2d_sat.cpp)。定理内容:两个凸集相交,当且仅当它们在所有潜在分离轴上的投影都重叠。对凸多边形,潜在分离轴只需要取两个多边形所有边的法线(边数之和那么多条),不用穷举所有方向——这是凸多边形版本的简化,曲面(圆)需要额外处理。
对每条候选轴 $\hat{a}$,把两个多边形顶点投影上去得到区间 $[s_1^{\min}, s_1^{\max}]$ 和 $[s_2^{\min}, s_2^{\max}]$,重叠量(MTV,minimum translation vector 的模长):
$$ d(\hat{a}) = \min(s_1^{\max}, s_2^{\max}) - \max(s_1^{\min}, s_2^{\min}) $$
存在某条轴使 $d(\hat{a}) < 0$,形状分离,判定结束(提前退出,这是 SAT 平均快的原因);所有轴 $d \ge 0$,则取 $d$ 最小的那条轴作为分离方向,$d$ 最小值作为穿透深度。MTV 方向指向让两个形状分开的最短路径——求解器的穿透修正(positional correction)直接用它。
SAT 的实现细节有一个值得抄的地方:投影不需要真的做向量点积排序——对每个多边形,沿轴的极值点只需要扫一遍顶点取 min/max,$O(n)$,n 是顶点数。碰撞对的总成本是 $O(n_1 + n_2)$ 每轴 × 轴数,方盒子对盒子永远是 4 条轴,常数极小。Godot 的 CollisionPolygon2D 要求凸分解(或引擎自动凸分解)就是 SAT 的前提:非凸形状 SAT 不适用。
2D 求解器的简化:标量惯量
3D 的角速度是向量,惯量是 $3\times 3$ 张量,冲量方程里 $\Delta\omega = I^{-1}(r \times J)$ 要算叉积和张量乘法。2D 世界里旋转只有一个轴(垂直屏幕),角速度是标量 $\omega$,惯量是标量 $I = \int r^2\, dm$。矩形绕质心的转动惯量 $I = \frac{1}{12}m(w^2 + h^2)$,圆盘 $I = \frac{1}{2}mr^2$——都是闭式解。
对接触求解的影响:接触冲量的法向分量方程里,含角速度的项从"张量乘叉积"退化成标量 $r^2/I$。一个盒子角上受到法向冲量,3D 里它的角速度变化方向要解三维方程,2D 里就是 $\Delta\omega = (r \times J)/I$ 一个除法。约束求解的有效质量计算(第 10 篇那个 $J M^{-1} J^T$)在 2D 里是 $2\times 2$ 到 $3\times 3$ 的小标量矩阵,几行代码算完。
同样简化的还有形状距离查询:2D 里没有真正的"GJK"(那是 3D 凸集距离的方法),圆-多边形、胶囊-多边形的窄相全是解析几何,代码路径短、分支少,这对缓存友好,所以 Godot 2D 物理的窄相吞吐其实比 3D 侧好看不少。
架构同源,参数不同
step 流程与 3D 侧同构(servers/physics_2d/godot_space_2d.cpp):broad phase(2D 用 GodotHashGrid 的 2D 版,broadphase_2d_sw_hash_grid.h)→ island → integrate → 迭代求解 → flush queries。_physics_process 的固定步长累加器(第 13 篇)对 2D 3D 一视同仁,physics_ticks_per_second 同时管两边。
差别在默认参数和求解强度:2D 默认 solver 迭代与 3D 相同量级,但因为每轮迭代便宜得多,同样迭代数下 2D 能维持更稳的堆叠。实测一个直观感受:2D 里堆 50 个盒子比 3D 里堆 50 个盒子稳得多,一半功劳是标量惯量让接触解耦干净,另一半是 2D 接触的几何(永远是一条线段)没有 3D 面-面接触的冗余自由度。
要吐槽一句:2D 没有 Jolt 的竞争,Godot Physics 2D 的迭代优化动力不如 3D。4.x 里 2D 侧的 CCD(continuous_cd_mode)、one-way collision 的行为偶有边角 bug(高速小物体穿过薄平台),排查思路倒是和 3D 一样:先看是不是采样间隔问题(第 6 篇的判据 $v\Delta t > s$ 在 2D 完全适用),是就提帧率或换大形状。
选型提醒
2D 游戏选物理前要明白一件事:RigidBody2D 的真实表现由 Godot Physics 2D 决定,没有 Plan B。对物理表现要求高的 2D 项目(精确弹球、格斗游戏推挤),两条路:一是把物理帧率拉高(2D 物理便宜,120Hz 成本也可忽略),用帧率换稳定;二是关键的确定性逻辑(弹珠台的角度计算)自己写运动学,物理引擎只做表现层。把引擎当确定性合同用,2D 3D 都危险(第 19 篇细说)。
本文基于 Godot 4.x servers/physics_2d/collision_solver_2d_sat.cpp(SAT 窄相)与 godot_space_2d.cpp(step 流程),惯量公式出自经典力学闭式解。