物理引擎
概述
物理引擎是游戏和模拟应用的核心组件,负责模拟现实世界中的物理现象,包括重力、碰撞、摩擦、弹力、约束等。在 Web 前端领域,Matter.js、Planck.js、Ammo.js 等物理引擎为浏览器提供了强大的物理模拟能力。本文将重点介绍物理引擎的核心概念,并以 Matter.js 为例演示如何使用物理引擎构建交互式应用。
物理引擎核心概念
刚体
刚体(Rigid Body)是物理引擎中最基本的模拟对象,代表一个不可变形的固体。每个刚体具有以下属性:
- 位置与旋转:决定刚体在空间中的坐标和朝向
- 速度与角速度:描述刚体的运动状态
- 质量与密度:决定惯性、重力响应等
- 摩擦力与恢复系数:决定碰撞时的能量损失与反弹
- 形状:可以是矩形、圆形、多边形等碰撞体
刚体按行为分为两类:
- 动态刚体:受重力、碰撞等力的影响,会主动运动
- 静态刚体:位置固定,不受力的影响,但可以作为动态刚体的碰撞对象
碰撞检测
碰撞检测是物理引擎最核心、最复杂的部分。Matter.js 使用 Broad Phase + Narrow Phase 的两阶段策略:
- Broad Phase(宽阶段):使用空间哈希或包围盒树快速筛选潜在碰撞对,跳过明显不相交的对象
- Narrow Phase(窄阶段):对候选碰撞对执行精确的分离轴定理(SAT)检测,计算碰撞点、法线和穿透深度
约束
约束(Constraint)限制刚体之间的相对运动。Matter.js 支持的约束类型包括:
距离约束(Distance Constraint):固定两点之间的距离,类似一根不可伸缩的杆子:
const constraint = Matter.Constraint.create({
bodyA: body1,
bodyB: body2,
length: 100,
stiffness: 0.8,
damping: 0.05
});固定约束(Point Constraint):将一个点固定在空间中的特定位置,或固定到另一个刚体的特定点。
鼠标约束(Mouse Constraint):将鼠标位置通过约束连接到物体,实现拖拽交互。
关节/马达
Matter.js 没有独立的关节系统,关节效果通过约束组合实现。Planck.js(Box2D 的 JavaScript 移植)则提供更完善的关节类型:
- 旋转关节(Revolute Joint):允许围绕一个公共点旋转
- 棱柱关节(Prismatic Joint):允许沿一个轴滑动
- 距离关节(Distance Joint):固定两点间的距离
- 焊接关节(Weld Joint):将两个刚体固定在一起
- 马达关节(Motor Joint):施加旋转力
Matter.js 常用 API
引擎
const engine = Matter.Engine.create({
gravity: { x: 0, y: 1 }
});世界
World 管理所有刚体和约束:
const body = Matter.Bodies.rectangle(200, 100, 40, 40);
Matter.Composite.add(engine.world, body);刚体创建
// 矩形
const box = Matter.Bodies.rectangle(x, y, width, height, options);
// 圆形
const ball = Matter.Bodies.circle(x, y, radius, options);
// 多边形
const polygon = Matter.Bodies.polygon(x, y, sides, radius, options);
// 复合体
const compound = Matter.Body.create({
parts: [part1, part2],
options
});渲染
Matter.js 内置的 Render 模块提供基础渲染,适合原型开发。生产环境通常会同步物理位置到自定义渲染器(如 PixiJS 或 Three.js):
const render = Matter.Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false,
background: '#0a0a1a'
}
});
Matter.Render.run(render);事件系统
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach(pair => {
console.log('碰撞:', pair.bodyA.label, pair.bodyB.label);
});
});软体模拟
Soft Body 原理
软体模拟通常使用 Spring-Mass 模型:将软体离散为一系列质量点(Mass Point),质量点之间通过弹簧约束连接。每个质量点受重力、弹簧力和阻尼力的共同作用。
Verlet 积分
Verlet 积分是布娃娃和软体模拟中常用的数值积分方法,相比欧拉积分具有更好的能量守恒特性:
// Verlet 积分公式
x(t + dt) = 2 * x(t) - x(t - dt) + a(t) * dt²Verlet 积分的优势在于:
- 长期稳定性更好,能量不易发散
- 约束可以迭代求解,实现简单
- 隐式处理了速度信息(无需显式存储速度)
布料模拟原理
布料模拟是软体模拟的典型应用。布料被建模为粒子网格,粒子间通过约束连接:
- 结构约束:水平与垂直方向连接相邻粒子,保持布料的基本形状
- 剪切约束:对角线方向约束,防止布料过度变形
- 弯曲约束:跨行/跨列约束,控制布料的弯曲刚度
布料模拟的悬挂方式决定了视觉效果。通常将布料顶部的一行或两个角固定到空间某点,其余部分受重力自然下垂。
在 Matter.js 中实现布料模拟,需要使用大量小球和约束构成网格:
const cols = 15, rows = 12, spacing = 25;
const particles = [];
// 创建粒子网格
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const p = Matter.Bodies.circle(x, y, 3, particleOptions);
particles.push(p);
}
}
// 创建约束连接
// 水平、垂直及对角线约束这种实现方式会产生大量约束对象,性能取决于粒子数量和约束求解迭代次数。
物理引擎选型对比
| 引擎 | 语言 | 特点 | 适用场景 |
|---|---|---|---|
| Matter.js | JS | 轻量、API 简洁、内置渲染器 | 2D 原型、中小型项目 |
| Planck.js | JS | Box2D 的 JS 移植,功能完善 | 需要关节、复杂碰撞的 2D 游戏 |
| Ammo.js | JS | Bullet 的 Emscripten 移植 | 3D 物理模拟 |
| Cannon-es | JS | 纯 JS 3D 引擎,现代 API | Three.js 生态的 3D 物理 |
| Rapier | Rust/JS | 高性能,WASM 编译 | 高性能 Web 游戏 |
| Box2D (C++) | Native | 工业级标准 | 原生 2D 游戏 |
选型建议:
- 快速原型或中小型 2D 项目:选择 Matter.js,API 直观,上手快
- 需要复杂关节的 2D 游戏:选择 Planck.js,继承 Box2D 的关节系统
- Web 3D 应用:选择 Cannon-es 搭配 Three.js
- 追求极致性能:选择 Rapier(WASM)或 Ammo.js
实际案例:物理沙盒
下面是一个基于 Matter.js 的交互式物理沙盒 Demo,展示了刚体创建、布料模拟和鼠标交互的综合应用:
总结
物理引擎为 Web 应用带来了丰富的交互可能性。理解刚体、碰撞检测、约束等核心概念,掌握 Matter.js 等常用引擎的 API 使用方法,能够帮助开发者快速构建游戏原型、物理模拟和交互式可视化应用。在实际项目中,应根据性能需求和功能复杂度合理选择物理引擎,并在开发过程中持续优化碰撞检测和约束求解的性能。