怎么使用 Matter.js 创建不规则多边形刚体

作者:半夏时间:2026-11-03 10:05:18浏览:137

全面讲解在 Matter.js 2D 物理引擎中创建不规则多边形与凹多边形刚体的完整流程。涵盖 poly-decomp 凸分解算法依赖集成、顶点数据构建、质心偏移校准机制与完整实操代码。

怎么使用 Matter.js 创建不规则多边形刚体
图 0:Matter.js 不规则多边形刚体、凹凸分解与物理世界交互示意图
在 Matter.js 2D 物理引擎中,常规的规则形状可以通过 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)的结构将它们绑定在一起,共享统一的位置、速度、角速度与质心动量。

怎么使用 Matter.js 创建不规则多边形刚体
图 1:凸多边形原生 SAT 判定与凹多边形多部件凸分解对比

二、创建不规则多边形刚体的全流程步骤

实现一个完整的不规则刚体,必须按照“顶点采集 → 依赖挂载 → 刚体生成 → 质心校准 → 加入物理世界”这一顺序执行。任何顺序倒置(例如未注册 decomp 库直接构建凹多边形)都会导致引擎报错或刚体轮廓退化。

怎么使用 Matter.js 创建不规则多边形刚体
图 2:使用 Matter.js 创建不规则多边形刚体的五步标准流程

步骤 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)的左上角。
怎么使用 Matter.js 创建不规则多边形刚体
图 3:顶点局部坐标系重构与世界质心位置校准机制

精确对齐目标位置的代码方案

如果业务需求要求将多边形的左上角严格对齐到屏幕的 (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 的控制参数与诊断技巧至关重要:

怎么使用 Matter.js 创建不规则多边形刚体
图 4: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 的子凸包线框全部绘制出来,能够一眼定位耳切分割是否符合预期。
作者最新文章
相关文章
更多