用 JavaScript 在浏览器里还原真实世界的物体运动,核心是把物理学里的位移、速度和加速度关系转化为每帧更新的数值计算。很多交互网页里的弹球、掉落方块其实就是一套微型物理引擎在背后跑循环。我们不需要一上来就搬出复杂的刚体动力学,只要搞懂二维平面上的质点运动与轴对称反弹,就能做出可玩的碰撞模拟。

运动学基础与矢量运算的实现
在二维空间里,物体的位置、速度和加速度都可以用含有 x 与 y 分量的矢量表示。物理学公式位置等于初位置加速度乘时间加二分之一加速度乘时间平方,在代码里通常被拆成每个坐标轴单独累加,这样更方便处理。我们会用一个对象保存物体的状态,包括坐标、速度以及质量等属性,然后在每一帧根据时间差更新它们。
时间步长一般采用上一次绘制到本次绘制的间隔,但为了稳定,很多简单引擎会固定步长,比如假设每秒六十帧,每帧对应十六点七毫秒。下面这段代码展示了如何定义物体结构并利用欧拉积分更新位置。欧拉积分是最直接的数值积分方式,它用当前速度推算下一帧位置,虽然不够精确但足够应付轻量模拟。
// 定义一个运动物体
function createBody(x, y, vx, vy, radius) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
radius: radius,
mass: 1
};
}
// 使用固定步长更新位置
function updateBody(body, dt) {
// dt 为秒,例如 0.016
body.x = body.x + body.vx * dt;
body.y = body.y + body.vy * dt;
// 这里暂未加入加速度,后面扩展重力时再补充
}
上面的 createBody 函数把物体抽象成带有横纵坐标和分速度的质点,updateBody 则是最基础的运动推进。实际开发中,我们会把加速度也放进更新逻辑,比如重力就是给 vy 持续加上一个常数。矢量运算的优势在于 x 与 y 完全独立,遇到斜向运动也不会增加复杂度。
边界碰撞检测与反弹处理
当物体飞出画布或者撞到墙壁时,最简单的方法是判断坐标是否越界,若越界就把对应方向的速度取反,并让物体回到边界内。这种方法叫离散碰撞检测,它每帧检查一次状态,实现容易,但在速度过快时可能穿过薄墙,也就是穿透现象。对于简单演示,穿透可接受,若要求严格则要采用连续检测或缩小步长。
下面示例在画布宽高范围内让圆球碰到四边就反弹,同时引入了重力加速下落。反弹系数用变量存储,值小于一表示有能量损耗。注意代码中比较的是物体圆心加减半径后的边界,这样才能让球体表面而非圆心贴合墙壁。
const canvasWidth = 800;
const canvasHeight = 400;
const restitution = 0.8; // 反弹保留百分之八十速度
function handleBoundary(body) {
if (body.x - body.radius < 0) {
body.x = body.radius;
body.vx = -body.vx * restitution;
} else if (body.x + body.radius > canvasWidth) {
body.x = canvasWidth - body.radius;
body.vx = -body.vx * restitution;
}
if (body.y - body.radius < 0) {
body.y = body.radius;
body.vy = -body.vy * restitution;
} else if (body.y + body.radius > canvasHeight) {
body.y = canvasHeight - body.radius;
body.vy = -body.vy * restitution;
}
}
// 在每帧中先加重力再更新再处理边界
function step(body, dt) {
body.vy = body.vy + 980 * dt; // 重力近似 980 像素每平方秒
updateBody(body, dt);
handleBoundary(body);
}
这种边界方案只适合物体与轴对齐的静态墙面互动。它的优点是计算量极小,手机浏览器也能轻松跑几十个小球。缺点是无法处理两个运动物体之间的互相碰撞,那需要引入碰撞法向量与动量守恒计算,我们下一个小节来补足这块。
多物体碰撞与动量交换原理
如果两个圆球在运动中相遇,就要检测它们圆心距离是否小于半径之和。一旦相交,就说明发生碰撞。处理这种碰撞不能简单把速度反向,而要根据两球连心线方向分解速度,再按质量做一维弹性或非弹性碰撞公式交换法向分量,切向分量保持不变。这样球才会看起来一个撞开另一个而不是穿过去。
以下代码演示了两圆碰撞的检测与基于一维冲量法的响应。我们假设两球质量相同,碰撞完全弹性,仅交换法向速度。实际项目里会把质量比带入公式,并用 restitution 控制非弹性损耗。这种算法属于圆形碰撞,因为圆没有旋转和角落,计算最省事,是入门物理引擎的最佳练习。
function resolveCollision(a, b) {
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const minDist = a.radius + b.radius;
if (dist === 0 || dist >= minDist) return;
// 法向单位向量
const nx = dx / dist;
const ny = dy / dist;
// 相对速度在法向上的投影
const rvx = b.vx - a.vx;
const rvy = b.vy - a.vy;
const velAlongNormal = rvx * nx + rvy * ny;
if (velAlongNormal > 0) return; // 已经在分开
// 相同质量弹性碰撞,交换法向速度分量
const aNormal = a.vx * nx + a.vy * ny;
const bNormal = b.vx * nx + b.vy * ny;
const temp = aNormal;
a.vx = a.vx - aNormal * nx + bNormal * nx;
a.vy = a.vy - aNormal * ny + bNormal * ny;
b.vx = b.vx - bNormal * nx + temp * nx;
b.vy = b.vy - bNormal * ny + temp * ny;
// 简单分离防止粘连
const overlap = minDist - dist;
a.x = a.x - nx * overlap / 2;
b.x = b.x + nx * overlap / 2;
}
把前面单球边界逻辑和多球碰撞结合,再用 requestAnimationFrame 循环调用 step 与绘制,就得到一个能模拟重力、墙壁回弹和球间碰撞的小引擎。若要更真实,可加入空气阻力、旋转摩擦力,或把圆扩展成矩形并用分离轴定理处理。理解了这些底层模块,你就能读懂大部分开源二维物理库的设计思路,也能按需求自己裁剪功能,不必被庞大框架束缚。
JavaScript物理引擎碰撞检测修改时间:2026-08-13 07:42:33