Phaser.js 物理引擎选择:Arcade Physics 与 Matter.js 对比
深入对比 Phaser.js 中内置的 Arcade Physics 与 Matter.js 物理引擎,从底层碰撞模型、性能开销、API 配置到实战选型决策树,帮助开发者快速确定最适合游戏玩法的物理方案。
一、底层几何与碰撞检测原理对比
物理引擎在运行时的每一步计算,本质上都在回答两个问题:物体之间是否发生了几何重叠?如果发生重叠,应当以多大的冲量将它们推开并调整速度?Arcade Physics 与 Matter.js 在数学建模与算法原理上有着根本区别。
1. Arcade Physics:基于 AABB 的标量投影判定
Arcade Physics 采用的是轴对齐包围盒(Axis-Aligned Bounding Box, AABB)与圆形碰撞体。所谓“轴对齐”,意味着碰撞矩形的四条边始终严格平行于屏幕的 X 轴与 Y 轴。当两个物体进行碰撞测试时,引擎只需在 X 轴与 Y 轴上分别判断两个一维区间的投影是否存在重叠:
// AABB 一维区间重叠计算伪代码
const overlapX = (boxA.width + boxB.width) / 2 - Math.abs(boxA.centerX - boxB.centerX);
const overlapY = (boxA.height + boxB.height) / 2 - Math.abs(boxA.centerY - boxB.centerY);
const isColliding = (overlapX > 0) && (overlapY > 0);
这种计算只涉及简单的加减法与绝对值运算,时间复杂度为 O(1),计算效率极高。但这种简化也带来了天然局限:Arcade 的物理刚体不支持旋转碰撞。开发者在 Phaser 中修改 sprite.angle 或 sprite.rotation 旋转角色精灵时,旋转的仅仅是屏幕绘制的纹理图片,背后的碰撞盒子仍然水平直立。如果让一个旋转了 45 度的长方形物体下落,它会在视觉上悬空被地面挡住,因为它的外接 AABB 矩形早已触底。
2. Matter.js:基于 SAT 分离轴定理的多边形刚体
Matter.js 是一套完整的 2D 刚体物理引擎,其核心碰撞检测基于分离轴定理(Separating Axis Theorem, SAT)。SAT 规定:如果能找到一条轴线,使得两个凸多边形在该轴上的正交投影不重叠,那么这两个多边形必然没有发生碰撞。反之,如果在所有潜在的分离轴上投影均发生重叠,则两个物体必定相交。
对于任意凸多边形,所有的可能分离轴恰好是各条边的法线向量。Matter.js 遍历两个刚体的全部边法线,计算各顶点在法线上的点积投影区间。若发生碰撞,引擎根据最小重叠量(最小平移向量 MTV)推算出碰撞接触点、穿透深度以及法线冲量,并结合刚体的质量、转动惯量(Moment of Inertia)和重心位置,精确求解出线速度与角速度的瞬时变化。
二、Phaser.js 中两者的接入与编码实践
在 Phaser 3 中,物理引擎是以场景级插件(Scene Plugin)的形式注入的。开发者不仅需要在游戏主配置(GameConfig)中指定引擎模式,在具体的场景代码中,对象的创建语法与碰撞回调机制也截然不同。
1. 全局配置差异
在创建 Phaser.Game 实例时,通过 config.physics 字段定义默认物理引擎及其参数:
// 方案 A:Arcade Physics 配置
const arcadeConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 600 }, // 竖直重力加速度 (像素/秒²)
debug: true // 开启绿色碰撞边界线与速度箭头
}
},
scene: [PlayScene]
};
// 方案 B:Matter.js 刚体配置
const matterConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'matter',
matter: {
gravity: { y: 1 }, // Matter 的重力单位与步进积分尺度不同
debug: true, // 开启多边形线框、法线与质心显示
wireframes: true // 是否以纯线框模式渲染物理世界
}
},
scene: [PlayScene]
};
2. 实体创建与属性定义
在具体的 Scene 实例中,两者的调用工厂与刚体控制方法有明显区别:
// --- Arcade Physics 实体创建与控制 ---
createArcadePlayer() {
// 1. 创建物理精灵 (挂载 Phaser.Physics.Arcade.Body)
const player = this.physics.add.sprite(100, 450, 'player');
// 2. 基础动力学设置 (直接对线速度标量赋值)
player.setBounce(0.1);
player.setCollideWorldBounds(true);
player.setVelocityX(200); // 水平匀速 200 像素/秒
// 3. 静态地面组创建
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// 4. 碰撞与重叠绑定 (声明式 API,引擎自动做分离推挤)
this.physics.add.collider(player, platforms);
this.physics.add.overlap(player, coins, (p, coin) => {
coin.disableBody(true, true);
});
}
// --- Matter.js 刚体实体创建与控制 ---
createMatterPlayer() {
// 1. 创建刚体精灵 (挂载 MatterJS.BodyType)
// 可以直接传入任意多边形或切角矩形
const player = this.matter.add.sprite(100, 450, 'player', null, {
shape: {
type: 'fromVerts',
verts: '0 0 40 0 50 30 30 60 10 60', // 自定义凸多边形顶点
flagInternal: true
},
chamfer: { radius: 4 }, // 圆滑切角,避免移动时在平地微缝卡滞
friction: 0.05, // 表面摩擦系数
frictionAir: 0.01, // 空气阻力
restitution: 0.2 // 弹性碰撞恢复系数
});
// 2. 刚体驱动方式 (推荐施加作用力或设置线性冲量)
player.applyForce({ x: 0.02, y: 0 });
// 如果是横版主角,通常需要锁死角度,防止角色翻滚倒地
player.setFixedRotation();
// 3. 碰撞监听 (基于全局事件系统,需手动识别碰撞对)
this.matter.world.on('collisionstart', (event) => {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const { bodyA, bodyB } = pairs[i];
// 比对 GameObject 或 label
if (bodyA === player.body || bodyB === player.body) {
// 执行碰撞业务逻辑
}
}
});
}
三、性能开销与运行表现评估
性能差异是两个引擎选型时最坚硬的指标。在相同硬件和浏览器环境下,两者的计算耗时与吞吐量差异往往能达到一个数量级。
1. 算力复杂度与垃圾回收 (GC) 开销
- 宽相阶段 (Broadphase):Arcade 采用简易网格空间哈希(Spatial Grid Hash)过滤;Matter.js 内部采用动态 AABB 树(BVH / Bounding Volume Hierarchy)维护空间拓扑。
- 窄相阶段 (Narrowphase):Arcade 仅执行 4 次浮点数比较;Matter.js 执行多边形顶点遍历投影、接触点流形(Contact Manifold)构建,并进行多次约束迭代(Constraint Iterations)求解。
- 对象分配与内存:Arcade 的 Body 对象结构轻简,物理模拟过程中几乎不产生临时向量对象;Matter.js 在高频求解切线法线与冲量对时,会产生更多内部向量与事件对象,在移动端低端设备上更容易引发浏览器垃圾回收(GC Pauses)微卡顿。
2. 游戏操控手感(Game Feel)的区别
在横版平台动作游戏中,“真实”并不等同于“好玩”。大部分经典街机与横版游戏(如《超级马里奥》《蔚蓝》)都需要非牛顿力学的精准控制:按下方向键立刻以全速前进,松开按键瞬间定住,起跳瞬间垂直加速度均匀,下落时没有斜坡滑动惯性。
Arcade Physics 天然契合这种手感。它的速度向量由代码直接修改,没有地表静摩擦、动摩擦系数的黏滞干扰,也不会因为踩在平台边缘而发生物理翻滚倾覆。反观 Matter.js,由于刚体天然具有摩擦力和力矩,如果不做深度针对性调优,角色跳上墙壁时会因为侧壁摩擦力产生“滑滑梯”或被墙体挂住;站在台阶边缘时,重心一旦出界就会失衡倾倒。为了在 Matter.js 中还原街机跳跃手感,往往需要给角色绑定多个无摩擦的隐形传感器刚体(Sensors),额外增加了架构复杂度。
四、典型避坑指南与开发注意事项
初学者常犯的错误是调用 sprite.setAngle(45) 旋转了一个子弹或矩形木箱,误以为碰撞盒也随之倾斜。事实上 Arcade 的 AABB 碰撞盒仍然保持水平立正。若需要处理带倾角的投射物,应优先使用圆形刚体 body.setCircle(radius),或改用 Matter.js 进行真实刚体模拟。
无论是 Arcade 还是 Matter.js,默认都采用离散时间步进(Discrete Steps)。当子弹或弹球的速度过快(例如单帧位移大于碰撞物体的厚度),物体可能会在这一帧位于墙体左侧,下一帧直接跳到墙体右侧,从而发生穿墙。在 Matter.js 中,可以通过减小物理步长时间、调大场景的 positionIterations 与 velocityIterations,或将墙体碰撞盒加厚来规避;Arcade 中则可以通过射线检测(Raycast)或将高速物体拆分为多步微积分迭代来预防。
如果游戏地图大量依赖斜坡(45 度或 30 度倾斜走廊):Arcade 本身无法让角色平滑贴着斜面行走,必须依靠外部插件(如 Phaser Arcade Slopes)或者在 Tilemap 碰撞层中使用梯形逼近;如果项目核心特色是连贯的起伏山地越野或斜坡滑行,直接选用 Matter.js 并配合 Phaser 的 Tilemap Matter 碰撞转换器 this.matter.world.convertTilemap(map) 是最稳妥的路径。
五、选型决策指南与落地实施方案
在立项之初评估物理引擎时,可以按照以下决策树逐步排查游戏需求,避免陷入“杀鸡用牛刀导致性能受阻”或“后期推翻底层架构重构物理”的困境。
| 对比项 | Arcade Physics | Matter.js |
|---|---|---|
| 核心计算模型 | AABB (轴对齐包围盒) + 圆形 | SAT (分离轴定理) + 任意凸多边形刚体 |
| 旋转碰撞支持 | 不支持 (碰撞盒始终水平直立) | 完美支持 (具有真实角加速度与转动惯量) |
| 约束系统 (Joints) | 无 (需开发者手动计算数学公式) | 内置丰富约束 (弹簧、销钉、索链、复合体) |
| 高密度物体负载能力 | 极高 (同屏 1000~3000 活跃实体无压力) | 中等 (建议同屏动态刚体控制在 200~400 以内) |
| 传统平台跳跃调优 | 开箱即用,操控精准随心,无惯性侧滑 | 需手动压制摩擦、锁定角度并编写脚底传感器 |
| 首选适用场景 | 横版像素跳跃、弹幕射击、吃豆人/RPG、小游戏 | 物理解谜、推箱子碰撞翻滚、愤怒小鸟、载具越野 |
总结实施建议
- 以 Arcade Physics 为工程默认基准:除非在需求文档中明确看到“物理翻滚、关节索桥、斜坡动态滑行、切分堆叠”等字眼,否则一律将 Arcade 作为初始引擎。极简的 API 与极致的执行帧率能够大幅缩短前期开发周期。
- 多场景隔离策略:如果游戏中绝大多数关卡是街机弹幕,仅个别关卡是物理小游戏,Phaser 允许在不同的
Phaser.Scene中声明不同的物理引擎配置。开发者可以在战斗场景使用 Arcade,在解谜小游戏场景单独加载 Matter.js,兼顾极致性能与玩法多样性。
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09








