怎么使用 Matter.js 创建不规则多边形刚体
全面讲解在 Matter.js 2D 物理引擎中创建不规则多边形与凹多边形刚体的完整流程。涵盖 poly-decomp 凸分解算法依赖集成、顶点数据构建、质心偏移校准机制与完整实操代码。
Bodies.circle 与 Bodies.rectangle 快速创建,但若要构建具备拐角、凹陷或特定轮廓的不规则多边形刚体,则需要调用 Matter.Bodies.fromVertices 方法。由于 Matter.js 底层依赖分离轴定理(Separating Axis Theorem, SAT)计算碰撞,原生仅支持凸多边形;一旦遇到凹多边形,必须引入凸分解算法库(如 poly-decomp),自动将凹轮廓切分为多个凸多边形子部件并组成复合刚体(Compound Body)。本文将直接梳理创建不规则刚体的完整链路、环境准备、顶点提取、质心偏移校准机制及实战调试方案。
一、不规则多边形刚体的底层原理与限制
在 2D 刚体动力学中,多边形依据内角分布被严格划分为凸多边形(Convex Polygon)与凹多边形(Concave Polygon)。理解这一几何差异,是掌握 Matter.js 不规则物理对象构建的前提:
- 凸多边形:多边形内部任意两个顶点的连线均完全位于多边形内部,所有内角均严格小于 180°。物理引擎可以直接采用分离轴定理(SAT),在多边形的各条外边法线方向上进行投射。如果存在任意一条法线使得两者的投影不重叠,则两者必定未发生碰撞。SAT 计算量轻量且判定结果完全确定。
- 凹多边形:存在至少一个大于 180° 的“凹陷角”(Reflex Vertex)。此时凹陷区域会在特定投射方向上产生遮挡盲区,直接使用 SAT 算法会导致碰撞体积失真、穿透或虚假相交。
为了在不牺牲物理性能与确定性的前提下支持凹形多边形,Matter.js 采用了凸分解(Convex Decomposition)策略:借助耳切法(Ear Clipping)或贝叶斯近似凸划分算法,将一个复杂的凹多边形拆解成若干个首尾相接、互不重叠的小凸多边形,再通过复合刚体(Compound Body)的结构将它们绑定在一起,共享统一的位置、速度、角速度与质心动量。
二、创建不规则多边形刚体的全流程步骤
实现一个完整的不规则刚体,必须按照“顶点采集 → 依赖挂载 → 刚体生成 → 质心校准 → 加入物理世界”这一顺序执行。任何顺序倒置(例如未注册 decomp 库直接构建凹多边形)都会导致引擎报错或刚体轮廓退化。
步骤 1:引入必要的依赖库
Matter.js 核心库自身不内置凸分解算法。创建凹多边形刚体前,必须引入 poly-decomp。根据项目的开发形态,通常有两种集成方式:
- 浏览器 CDN / 传统 Script 引入:在 HTML 中先引入
decomp.min.js,再引入matter.min.js。此时decomp会自动挂载到全局window.decomp,Matter.js 内部会自动检测并调用。 - 现代工程化环境(Vite / Webpack / ES Modules):通过 npm 安装
matter-js与poly-decomp。在模块化环境下,全局 window 对象上可能没有 decomp,必须通过Common.setDecomp()显式注入。
// npm 依赖安装命令:
// npm install matter-js poly-decomp
import Matter from 'matter-js';
import decomp from 'poly-decomp';
// 关键步骤:显式为 Matter.js 注入凸分解引擎
Matter.Common.setDecomp(decomp);
poly-decomp 或注入失败,直接向 Bodies.fromVertices 传入凹多边形顶点,Matter.js 控制台会输出警告:Matter.Bodies.fromVertices: decomp library not found, concave vertices may not be supported,并且引擎会自动计算外围凸包(Convex Hull),导致原本凹进去的区域变成实心碰撞体。
三、定义顶点数据与调用 Bodies.fromVertices
构建不规则多边形需要提供一系列 2D 坐标点。在 Matter.js 中,顶点数据有两种主流获取途径:
1. 手工定义顶点坐标数组
顶点数据必须是以 { x: Number, y: Number } 组成的对象数组。点集的排列顺序非常关键,必须沿着顺时针(Clockwise)或逆时针(Counter-Clockwise)连续排列,不能出现线条自相交。
// 手动定义一个“凹形 L 构件”或“倒 T 构件”的顶点集合
const lShapeVertices = [
{ x: 0, y: 0 },
{ x: 60, y: 0 },
{ x: 60, y: 120 },
{ x: 140, y: 120 },
{ x: 140, y: 180 },
{ x: 0, y: 180 }
];
2. 从 SVG 矢量路径提取顶点
对于更复杂的艺术图形或游戏轮廓,手动打点极其繁琐,通常在 Illustrator 或 Figma 中绘制出路径,导出为 SVG <path>,然后使用 Matter.Svg.pathToVertices 进行采样转换:
// 获取 DOM 中的 SVG 路径元素
const pathElement = document.querySelector('#irregular-contour');
// 将 SVG 路径按指定采样步长(sampleLength 越小精度越高,顶点越多)转换为顶点数组
const vertices = Matter.Svg.pathToVertices(pathElement, 15);
3. 调用 Bodies.fromVertices 方法
获得顶点数组后,即可调用核心构造函数生成刚体对象:
const irregularBody = Matter.Bodies.fromVertices(
300, // x: 刚体质心在物理世界的初始 X 坐标
200, // y: 刚体质心在物理世界的初始 Y 坐标
lShapeVertices, // vertexSets: 顶点数组或多组顶点构成的嵌套数组
{ // options: 刚体物理属性对象
friction: 0.05,
restitution: 0.6,
density: 0.002,
render: {
fillStyle: '#438A80',
strokeStyle: '#2C3E50',
lineWidth: 1
}
},
true, // flagInternal: 是否标记内部拼接缝(默认 false)
0.01, // removeCollinear: 移除共线冗余点的容差(默认 0.01)
10 // minimumArea: 最小凸部件面积阈值(默认 10)
);
四、质心偏移与绝对定位校准
初学者在使用 Bodies.fromVertices 时最常遇到的困惑是:“为什么我传入的 (x, y) 坐标,多边形并没有出现在我想要的位置?”
这是因为多边形的输入点集通常是基于设计稿坐标系(例如左上角为 (0, 0))。然而在物理仿真中,刚体的旋转和受力必须围绕其物理质心(Centroid)展开。
Bodies.fromVertices 在解析顶点时,会首先计算整个多边形的几何质心 (Cx, Cy),并将所有顶点的局部坐标减去该质心向量。因此:
- 多边形在 Matter.js 局部坐标系中的原点
(0, 0)变成了它的质心。 - 开发者在参数中传入的
(x, y),代表的是这个质心在物理世界中的摆放坐标,而不是顶点包围盒(Bounding Box)的左上角。
精确对齐目标位置的代码方案
如果业务需求要求将多边形的左上角严格对齐到屏幕的 (targetX, targetY),可以在刚体生成后,读取 body.bounds(刚体当前外接矩形)与 body.position 的差值进行修正:
// 1. 先在初始位置创建刚体
const body = Matter.Bodies.fromVertices(0, 0, vertices, options);
// 2. 计算当前包围盒左上角与质心的实际偏移差值
const offsetX = body.position.x - body.bounds.min.x;
const offsetY = body.position.y - body.bounds.min.y;
// 3. 将刚体精准平移到期望的左上角对齐位置 (targetX, targetY)
Matter.Body.setPosition(body, {
x: targetX + offsetX,
y: targetY + offsetY
});
五、完整可运行代码示例
下面是一个可以直接保存为 .html 文件并在浏览器中独立运行的完整示例。示例中引入了 CDN 版的 Matter.js 与 poly-decomp,在物理画布中生成了一个凹形箭头状的不规则刚体,演示其在重力作用下的下落、碰撞反弹与旋转效果:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Matter.js 不规则多边形刚体运行演示</title>
<!-- 1. 优先加载 poly-decomp 凸分解库 -->
<script src="https://cdn.jsdelivr.net/npm/poly-decomp@0.3.0/build/decomp.min.js"></script>
<!-- 2. 加载 Matter.js 物理引擎核心库 -->
<script src="https://cdn.jsdelivr.net/npm/matter-js@0.19.0/build/matter.min.js"></script>
<style>
body { margin: 0; background: #1E293B; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { border: 1px solid #334155; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
</style>
</head>
<body>
<script>
const { Engine, Render, Runner, Bodies, Composite, Common } = Matter;
// 显式确保 decomp 已挂载
if (typeof decomp !== 'undefined') {
Common.setDecomp(decomp);
}
// 创建物理引擎实例
const engine = Engine.create();
// 创建画布渲染器
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false, // 设为 false 以便观察实心着色与碰撞体积
background: '#0F172A'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
// 定义凹形多边形顶点(凹形箭头轮廓,逆时针闭合)
const arrowVertices = [
{ x: 40, y: 0 },
{ x: 80, y: 60 },
{ x: 55, y: 60 },
{ x: 55, y: 130 },
{ x: 25, y: 130 },
{ x: 25, y: 60 },
{ x: 0, y: 60 }
];
// 生成不规则刚体
const irregularBody = Bodies.fromVertices(400, 100, arrowVertices, {
restitution: 0.5,
friction: 0.1,
render: {
fillStyle: '#438A80',
strokeStyle: '#6EE7B7',
lineWidth: 1.5
}
});
// 创建地面与左右挡板(静态刚体)
const ground = Bodies.rectangle(400, 580, 810, 40, { isStatic: true, render: { fillStyle: '#334155' } });
const leftWall = Bodies.rectangle(10, 300, 20, 600, { isStatic: true, render: { fillStyle: '#334155' } });
const rightWall = Bodies.rectangle(790, 300, 20, 600, { isStatic: true, render: { fillStyle: '#334155' } });
// 将所有刚体加入物理世界
Composite.add(engine.world, [irregularBody, ground, leftWall, rightWall]);
</script>
</body>
</html>
六、核心参数调优与常见问题排查
在实际项目落地中,不规则多边形常因顶点质量参差不齐而出现物理穿透、抖动或帧率骤降。掌握 Bodies.fromVertices 的控制参数与诊断技巧至关重要:
1. 关键参数参考表
| 参数名 | 默认值 | 数据类型 | 作用与调优建议 |
|---|---|---|---|
vertexSets |
必填 | Array | 顶点集合。如果是多组分离轮廓可传入包含多个顶点集的嵌套数组。 |
flagInternal |
false | Boolean | 设为 true 时,会将耳切法拆分出的内部拼接缝边标记为 internal,防止物体在内部缝隙处产生不合理的微碰撞。 |
removeCollinear |
0.01 | Number | 共线过滤阈值。SVG 导出的矢量图形常包含过密的三点共线,设为 0.01 ~ 0.05 可过滤无效点,大幅降低计算开销。 |
minimumArea |
10 | Number | 最小部件面积限制。低于此面积的狭长微小凸包会被自动剔除,防止引起物理积分不稳定的微小碎屑。 |
2. 常见问题排查与避坑要点
- 报错 “decomp library not found” 或图形坍塌为外接凸包:检查
poly-decomp库是否已正确加载。在构建工具环境下,必须在调用fromVertices之前执行Matter.Common.setDecomp(decomp)。 - 多边形发生穿透或剧烈弹起震颤:通常是由于顶点中存在自相交线段(Self-intersection)或者极度狭长的尖角。耳切算法对自相交线段非常敏感,请确保多边形轮廓严格闭合且线条无交叉,并适当调大
minimumArea。 - 顶点缠绕方向混淆:顶点可以顺时针也可以逆时针提供,但必须连贯闭合。如果混合了不同方向的顶点集,
decomp.quickDecomp可能会生成错误的面朝向,导致法向量反转。 - 开发阶段开启线框调试:在排查碰撞异常时,务必临时将
Render.create中的options.wireframes设为true。此时 Canvas 会将内部所有body.parts的子凸包线框全部绘制出来,能够一眼定位耳切分割是否符合预期。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13








