插件及工具

Matter.js一款2D物理引擎

试玩一下Matter.js并通过案例理解其基础模块

Yixuan Lang
2022-05-30
6 min

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

matter

最近对 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 提供了很多特性,来去帮助我们模拟一些物理场景和操作。

image-20220529231833961

# 基础核心模块

大多数的物理引擎对于物理模拟的要素都有着相近的概念,不同的引擎差别在于使用的方式,功能的全面性,模拟的精细度等层面,下面就先从物理世界的基础核心模块讲起。

image-20220529232741609

# 👉 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,这个模块让你增加鼠标与物体之间的”约束”,透过建立物体与鼠标的限制,就可以让使用者透过鼠标与你创建的物体互动。

# 使用案例

上面列出的基本核心模块,在没有例子的铺垫下理解起来会比较抽象晦涩,下面将会结合一个小案例来理解上述模块的基本使用。先来看一下最终案例的效果 👇

demo

# 🌟 初始化项目

# 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),最后渲染出来的效果就是下面这个样子 👇

image-20220530115535157

# 4. 启动

到目前为止,我们设定好了 EngineRender 的实例,代表我们已经准备好了一个虚拟的世界,然而光是准备好还不够,我们需要“启动”它。

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 模块来帮助我们生成多个物体所组成的形状。

image-20220530130146696

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]);

组合形状初始掉落时的样子 👇

image-20220530145822110

# 🌟 增加障碍、重力、鼠标约束

上面已经完成该案例的基本部分,为了让其更加有趣以及贴近真实的物理效果,需要增加一些其他的操作。

# 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;

demo2

# 3. 鼠标约束

使用MouseConstraint增加鼠标与物体之间的”约束”,可以让使用者透过鼠标与你创建的物体进行互动。

const mouseControl = MouseConstraint.create(engine, {
  element: sectionTag,
  constraint: {
    render: {
      visible: false, // visible 代表着滑鼠的拖拉轨迹会不会呈现出来
    },
  },
});

看一下设置后的效果,在拖拉小球时,就会产生相应的效果

demo3

# 总结

试玩了一下 Matter.js,觉得还是十分有趣的,但是并没有对其原理方面有更多的探究。其官方文档也是英文的,其功能与特性的讲解也比较简洁。后续如果有时间会继续研究一下,试着做个小的游戏~~~😎

# 文章参考

Matter.js 2D 物理引擎试玩报告

使用 Matter.js 2D 物理引擎製作動畫

【一统江湖的大前端(8)】matter.js 经典物理

Day29 - 物理模擬 - 讓物件自然的落下碰撞 (使用 Matter.js)