第三章 牛顿力学基础:牛顿三大定律、力与加速度、动量与冲量
各位同学,欢迎来到第三章。说实话,牛顿力学这部分内容,你在高中物理课本里肯定见过。但我要告诉你——游戏引擎里的牛顿力学,和考试卷上的牛顿力学,完全是两码事。
考试考的是理想状态:光滑平面、无空气阻力、质点运动。游戏里呢?摩擦力、空气阻力、碰撞形变、玩家乱按键盘……这些乱七八糟的东西全得算进去。我当年刚入行时,把物理课本上的公式直接搬进代码,结果角色一跑起来就像在冰面上滑冰,根本没法玩。
所以这一章,咱们不讲虚的。直接上干货,讲清楚牛顿三大定律在游戏引擎里到底怎么用,以及那些坑你千万别踩。
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 力与加速度:从力到运动的完整链路
咱们把整个流程串起来看看:
- 收集所有力:重力、玩家输入力、弹簧力、摩擦力、碰撞力……
- 求合力:把所有力向量相加
- 算加速度:合力除以质量
- 更新速度:速度 += 加速度 × 时间步长
- 更新位置:位置 += 速度 × 时间步长
这个流程看起来简单,但每一步都有坑。比如重力,很多人直接写 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 之间,手感比较自然。
3.6 动量守恒:系统级别的检查工具
动量守恒定律说:系统不受外力时,总动量保持不变。这在游戏里是一个绝佳的调试工具。
我习惯在每帧结束时,计算所有物体的总动量。如果总动量突然变了,那一定是有外力没算对,或者碰撞处理时动量不守恒。有一次我发现总动量在缓慢增加,查了半天,原来是摩擦力方向算反了——摩擦力应该减速,结果变成了加速。
你想想看,如果没有动量守恒这个检查工具,这种 bug 可能要跑很久才能发现。所以我的建议是:在物理引擎里加一个动量监控器,每帧输出总动量,一旦发现异常立刻报警。
3.7 本章小结
牛顿三大定律是游戏物理引擎的基石。第一定律告诉我们「别乱加力」,第二定律告诉我们「力怎么产生运动」,第三定律告诉我们「力是成对出现的」。动量与冲量则是碰撞处理的核心工具,比直接用力去算要稳定得多。
我个人觉得,理解这些概念最好的方式就是动手写一个简单的物理模拟器——让一个小球在屏幕上弹跳,加上重力、摩擦力和碰撞。写完之后,你对这些公式的理解会比看十遍书都深刻。
下一章我们会深入刚体动力学,到时候这些基础概念会反复用到。嗯,先把这一章消化好,后面的路就好走了。