# Matter.js 一款 2D 物理引擎

最近对 Creative Coding 领域很感兴趣,也在不断的探索与实践中发现了一些很好玩的库以及工具。最近了解到了一款 2D 物理引擎——Matter.js。
Matter.js 是一个用于 Web 的 JavaScript 2D 物理引擎库,它相较于老牌的 Box2D 引擎库,Matter.js 更为轻量级,并且在性能和功能方面也毫不逊色,所提供的 API 也设计的简单好用。
在没有 Matter.js 前,你想去制作一个物理游戏不仅需要扎实的数学知识和物理知识,还需要通过编程语言表示出来让机器读懂。而有 Matter.js 就不一样了,它为开发者提供了许多的功能模块,通过简单易用的 API 就可以实现例如弹跳、碰撞、重力、滚动等物理效果。
该篇文章就让我们来具体体会一下 Matter.js 的功能及魅力吧~
# Matter.js 所支持的特性
Matter.js 提供了很多特性,来去帮助我们模拟一些物理场景和操作。
# 基础核心模块
大多数的物理引擎对于物理模拟的要素都有着相近的概念,不同的引擎差别在于使用的方式,功能的全面性,模拟的精细度等层面,下面就先从物理世界的基础核心模块讲起。
# 👉 Engine
模块包含了创建和处理引擎的方法,引擎是负责管理和更新模拟世界的控制器,引擎可以控制时间的缩放,可以检测所有的碰撞事件。
# 👉 World
透过此模块来创建一个模拟世界,可以微调世界中的一些属性,像是重力、边界等等,而一个世界当然是由多个 Bodies 所组成。
# 👉 Bodies
Bodies 模块提供你方法去生成一些物体,像是圆形物体、方形物体等等,你也可以传入 svg、img 去客制化物体形状与样式。产生的物体放入 World 中后就可以被 render 在画面上。
# 👉 Body
利用 Bodies 产生的物件可以利用 Body 模组来进行进一步的操控。透过 Body,你可以旋转、缩放、位移你的物体,也可以更改物体本身的密度、速度等等。换句话说,Body 让你调整物体的物理特性。
# 👉 Render
将实例渲染到 Canvas 中的渲染器,控制视图层的样式,它的主要作用是用于开发和调试,默认情况下 Matter.Render
将只显示物体的线框(轮廓),这对于开发和调试很有帮助,但如果需要使用到全局实体渲染则需要将线框模式关闭 render.options.wireframes = false
,另外它同样也适合制作一些简单的游戏,因为它包括了一些绘图选项、线框、向量、Sprite 精灵和视窗功能。
# 👉 Composites
这个模组有点像是 Bodies 模组,差别在于 Bodies 模组让你创建出 ”一个“ 物体,而 Composites 提供方法让你创建出多个物体所组合而成的物体,像是 Stack、Pyramid 或什至是 Car, Chain 等等常用的内建组合。
# 👉 Composite
如同 Body 对应于 Bodies,Composite 就是对应于 Composites 的模组,让你控制由 Composites 创建出的组合物体的物理特性。
# 👉 MouseConstraint
如同 Constraint,这个模块让你增加鼠标与物体之间的”约束”,透过建立物体与鼠标的限制,就可以让使用者透过鼠标与你创建的物体互动。
# 使用案例
上面列出的基本核心模块,在没有例子的铺垫下理解起来会比较抽象晦涩,下面将会结合一个小案例来理解上述模块的基本使用。先来看一下最终案例的效果 👇
# 🌟 初始化项目
# 1. 引入 matter.js
首先,需要下载开发版本或者稳定版定,并将脚本加入到页面中,即可开启旅程。
<script src="matter.js" type="text/javascript"></script>
你也可以使用包管理工具 NPM 直接下载
$ npm install matter-js
# 2. 导入模块
可以使用对象解构的方式,快速引入案例中所需要使用的 matter.js 模块
const {
Engine,
Render,
Bodies,
World,
MouseConstraint,
Composites,
Query,
} = Matter;
# 3. 创建 Engine 和 Render 实例
接着创建 instance,利用 Engine.create()
创造 Engine 实例,Render 的部分我们要指定使用的 engine、要渲染的 root element,以及宽高背景颜色等基本选项。更细部的 properties 可以参考官网文件。
// 用于使用matter.js进行渲染的区域
const sectionTag = document.querySelector("section.shapes");
const engine = Engine.create();
const renderer = Render.create({
element: sectionTag,
engine: engine,
options: {
height: h,
width: w,
background: "#000000",
wireframes: false,
pixelRatio: window.devicePixelRatio,
},
});
wireframes
属性:表示是否只是渲染物体的边框,默认值为 true。如果我们不设置该属性(或设置为 true),最后渲染出来的效果就是下面这个样子 👇
# 4. 启动
到目前为止,我们设定好了 Engine
与 Render
的实例,代表我们已经准备好了一个虚拟的世界,然而光是准备好还不够,我们需要“启动”它。
Engine.run(engine);
Render.run(renderer);
# 🌟 在虚拟的物理世界中放入物体
现在 Engine 与 Render 都启动了,虚拟的物理世界已上线接下来只要往里面丢入物体就好了。
在案例中,我们主要有两个部分的物体
- 中间的大圆形
- 掉落的形状
# 1. 中间的大圆
使用Bodies.circle创建一个圆形,传入相应的参数。并将其加入到World
中,World 相当于是模拟的这个物理世界的容器,所有 Boides 创建的物体需要在这个容器中才会产生效果
const bigBall = Bodies.circle(w / 2, h / 2, Math.min(w / 4, h / 4), {
isStatic: true,
render: {
fillStyle: "#ffffff",
},
});
World.add(engine.world, [bigBall]);
# 2. 掉落的形状
案例初始时会有一堆掉落的形状,并不是单个的物体,这时我们可以借助 Composites 模块来帮助我们生成多个物体所组成的形状。

Composites.stack
前面六个参数可以定义一个 grid 空间,范例中我们在相对于 Render 设定范围的 x 轴 50px 与 y 轴 50px 的位置开始放置 stack,并定义该 grid 是 15 x 5 的格子,格子与格子之间需要设置间隙,将 columnGap 与 rowGap 设置 40 和 40。最后一个参数回调函数中创建组成形状的个体。
最后不要忘记将形状加入 World 中
const initialShapes = Composites.stack(50, 50, 15, 5, 40, 40, function(x, y) {
return createShape(x, y);
});
const createShape = function(x, y) {
const randomNum = Math.random();
if (randomNum > 0.5) {
return Bodies.rectangle(x, y, 38, 50, {
render: {
sprite: {
texture: "../assets/outline-2x.png",
xScale: 0.5,
yScale: 0.5,
},
},
});
} else {
return Bodies.circle(x, y, 25, {
render: {
sprite: {
texture: "../assets/ball.png",
xScale: 0.5,
yScale: 0.5,
},
},
});
}
};
World.add(engine.world, [bigBall, initialShapes]);
组合形状初始掉落时的样子 👇
# 🌟 增加障碍、重力、鼠标约束
上面已经完成该案例的基本部分,为了让其更加有趣以及贴近真实的物理效果,需要增加一些其他的操作。
# 1. 增加障碍物
小球在初始化掉落时,会有类似与落在一个容器中的效果,容器具有内壁,这种效果使用 Matter.js 来模拟也十分的简单,使用Bodies.rectangle 去创建内壁,控制物体的活动范围,这在制作游戏时也是很重要的一部分。利用
isStatic` 属性,让其固定住。
const ground = Bodies.rectangle(w / 2, h + 50, w + 100, 100, wallOptions);
const ceiling = Bodies.rectangle(w / 2, -50, w + 100, 100, wallOptions);
const leftWall = Bodies.rectangle(-50, h / 2, 100, h + 100, wallOptions);
const rightWall = Bodies.rectangle(w + 50, h / 2, 100, h + 100, wallOptions);
World.add(engine.world, [
bigBall,
ground,
ceiling,
leftWall,
rightWall,
initialShapes,
]);
# 2. 改变重力
可以通过engine.world.gravity
属性来更改其物理环境中的重力。模拟真实环境中物体可能会因为一些外界因素,导致其物体本身重力的平衡发生改变。
利用 requestAnimationFrame 方法不断的进行递归,去回调更改重力的方法,让其 engine.world.gravity 在水平和垂直方向上的重力在-1 ~ 1 之间波动.
let speed = 0;
const changeGravity = function() {
speed = speed + 0.001;
engine.world.gravity.x = Math.sin(time);
engine.world.gravity.y = Math.cos(time);
requestAnimationFrame(changeGravity);
};
changeGravity();
为了能够更明显的看到改变了重力的效果,我们可以将 speed 所叠加的值改的更加大一些,看一下效果
speed = speed + 0.1;
# 3. 鼠标约束
使用MouseConstraint
增加鼠标与物体之间的”约束”,可以让使用者透过鼠标与你创建的物体进行互动。
const mouseControl = MouseConstraint.create(engine, {
element: sectionTag,
constraint: {
render: {
visible: false, // visible 代表着滑鼠的拖拉轨迹会不会呈现出来
},
},
});
看一下设置后的效果,在拖拉小球时,就会产生相应的效果
# 总结
试玩了一下 Matter.js,觉得还是十分有趣的,但是并没有对其原理方面有更多的探究。其官方文档也是英文的,其功能与特性的讲解也比较简洁。后续如果有时间会继续研究一下,试着做个小的游戏~~~😎