第三章 牛顿力学基础:牛顿三大定律、力与加速度、动量与冲量

各位同学,欢迎来到第三章。说实话,牛顿力学这部分内容,你在高中物理课本里肯定见过。但我要告诉你——游戏引擎里的牛顿力学,和考试卷上的牛顿力学,完全是两码事。

考试考的是理想状态:光滑平面、无空气阻力、质点运动。游戏里呢?摩擦力、空气阻力、碰撞形变、玩家乱按键盘……这些乱七八糟的东西全得算进去。我当年刚入行时,把物理课本上的公式直接搬进代码,结果角色一跑起来就像在冰面上滑冰,根本没法玩。

所以这一章,咱们不讲虚的。直接上干货,讲清楚牛顿三大定律在游戏引擎里到底怎么用,以及那些坑你千万别踩。

3.1 牛顿第一定律:惯性定律

「物体在不受外力作用时,保持静止或匀速直线运动状态。」

嗯,这句话背起来容易。但在游戏里,你想想看——什么时候物体真的「不受外力」?几乎没有。重力永远在,空气阻力永远在,地面摩擦永远在。所以第一定律在游戏里的实际意义是:如果你想让物体保持当前速度,就别给它施加任何力

我见过不少新手,每帧都给物体施加一个「维持速度」的力,结果物体越跑越快,最后飞出了地图边界。其实你只需要在Update()里什么都不做,物体的速度自然就保留了——前提是你没有每帧重置速度。

核心要点: 惯性在游戏里表现为「速度的持续性」。除非你主动施加力或阻尼,否则物体的速度不会自己改变。

3.2 牛顿第二定律:F = ma

这才是游戏物理引擎的命根子。F = ma,力等于质量乘以加速度。说白了,就是「你推得越狠,它跑得越快;它越重,你推不动」。

在代码里,我们通常这样用:

// 每帧计算合力
Vector3 totalForce = CalculateAllForces();  // 重力、推力、摩擦力等

// 根据 F = ma 计算加速度
Vector3 acceleration = totalForce / mass;

// 更新速度
velocity += acceleration * deltaTime;

// 更新位置
position += velocity * deltaTime;

这里有个细节我特别想强调:deltaTime 必须乘在加速度上,而不是乘在力上。为什么?因为力是瞬时的,加速度也是瞬时的,只有速度和位置才需要积分。我曾经在项目里看到有人写成 velocity += (totalForce / mass) * deltaTime * deltaTime,结果物体的运动完全不对,调试了整整两天才发现问题。

我的习惯: 把质量单独拎出来作为一个参数,而不是硬编码到力的计算里。这样以后调整手感时,改质量比改力更方便。

3.3 牛顿第三定律:作用力与反作用力

「两个物体之间的作用力和反作用力,大小相等,方向相反。」

这条定律在碰撞处理里特别重要。两个球撞在一起,A 对 B 施加一个力,B 对 A 也施加一个大小相同、方向相反的力。如果你只处理了一个方向的力,那物体就会「穿模」——一个球把另一个球推走了,自己却纹丝不动。

我记得有一次做台球游戏,白球撞到红球后,红球飞出去了,白球却停在了原地。查了半天,发现我只给红球施加了碰撞力,忘了给白球施加反作用力。嗯,这种错误犯过一次就再也不会忘了。

避坑指南: 我曾经在实现碰撞响应时,只计算了「被撞物体」的受力,忽略了「主动撞的物体」的反作用力。结果就是动量不守恒,整个系统的能量越撞越多,最后物体自己加速飞走了。记住:作用力和反作用力必须成对出现

3.4 力与加速度:从力到运动的完整链路

咱们把整个流程串起来看看:

  1. 收集所有力:重力、玩家输入力、弹簧力、摩擦力、碰撞力……
  2. 求合力:把所有力向量相加
  3. 算加速度:合力除以质量
  4. 更新速度:速度 += 加速度 × 时间步长
  5. 更新位置:位置 += 速度 × 时间步长

这个流程看起来简单,但每一步都有坑。比如重力,很多人直接写 gravity = new Vector3(0, -9.8f, 0),但游戏里的重力加速度其实可以调——你想让角色跳得高一点,就把重力调小一点。说白了,物理引擎的「物理」是服务于「游戏性」的,不是服务于真实世界的。

力的类型 计算公式 常见用途
重力 F = m × g 下落、跳跃
摩擦力 F = μ × N 地面减速、滑动
弹簧力 F = -k × x 弹性碰撞、绳索
阻尼力 F = -b × v 空气阻力、缓冲

3.5 动量与冲量:碰撞处理的核心

动量 p = m × v,冲量 J = F × Δt。这两个概念在碰撞处理里比力本身更常用。

为什么?因为碰撞发生的时间极短,力的变化非常剧烈。如果你用 F = ma 去算,时间步长稍微大一点,结果就飞了。而用冲量-动量定理,我们可以直接算出碰撞前后的速度变化,不需要知道碰撞过程中力的具体变化曲线。

具体做法是这样的:

// 两个球碰撞,质量分别为 m1, m2,速度分别为 v1, v2
// 计算相对速度
Vector3 relativeVelocity = v1 - v2;

// 计算法线方向(从球2指向球1)
Vector3 normal = (pos1 - pos2).normalized;

// 计算法线方向上的相对速度分量
float relativeSpeed = Vector3.Dot(relativeVelocity, normal);

// 如果相对速度是分离的,就不处理
if (relativeSpeed > 0) return;

// 计算冲量大小(假设完全弹性碰撞,恢复系数 e = 1)
float impulseMagnitude = -(1 + e) * relativeSpeed / (1/m1 + 1/m2);

// 应用冲量
Vector3 impulse = impulseMagnitude * normal;
v1 += impulse / m1;
v2 -= impulse / m2;

这段代码我写了几百遍了。每次写的时候都会提醒自己:恢复系数 e 的取值范围是 0 到 1。e = 1 是完全弹性碰撞(像台球),e = 0 是完全非弹性碰撞(像两团泥巴粘在一起)。游戏里一般取 0.5 到 0.8 之间,手感比较自然。

关键公式: 冲量 J = Δp = m × Δv。碰撞处理的核心就是算出这个 J,然后分别应用到两个物体上。

3.6 动量守恒:系统级别的检查工具

动量守恒定律说:系统不受外力时,总动量保持不变。这在游戏里是一个绝佳的调试工具。

我习惯在每帧结束时,计算所有物体的总动量。如果总动量突然变了,那一定是有外力没算对,或者碰撞处理时动量不守恒。有一次我发现总动量在缓慢增加,查了半天,原来是摩擦力方向算反了——摩擦力应该减速,结果变成了加速。

你想想看,如果没有动量守恒这个检查工具,这种 bug 可能要跑很久才能发现。所以我的建议是:在物理引擎里加一个动量监控器,每帧输出总动量,一旦发现异常立刻报警。

实用技巧: 动量守恒也可以用来验证你的碰撞处理是否正确。两个球碰撞前后,总动量应该完全一致(在浮点误差范围内)。如果不一致,说明你的冲量计算有问题。

3.7 本章小结

牛顿三大定律是游戏物理引擎的基石。第一定律告诉我们「别乱加力」,第二定律告诉我们「力怎么产生运动」,第三定律告诉我们「力是成对出现的」。动量与冲量则是碰撞处理的核心工具,比直接用力去算要稳定得多。

我个人觉得,理解这些概念最好的方式就是动手写一个简单的物理模拟器——让一个小球在屏幕上弹跳,加上重力、摩擦力和碰撞。写完之后,你对这些公式的理解会比看十遍书都深刻。

下一章我们会深入刚体动力学,到时候这些基础概念会反复用到。嗯,先把这一章消化好,后面的路就好走了。