关于 phaser-matter
phaser-matter 是面向使用 Matter.js 物理引擎的 Phaser 4 开发者的参考技能,讲解复杂碰撞形状、约束、传感器及 Arcade Physics 之外的碰撞处理。
主要功能
- 01创建多边形和复合刚体
- 02设置刚体属性并施加力
- 03处理物理世界碰撞事件
- 04通过类别筛选碰撞
- 05使用传感器和约束
适用场景
- 01构建非矩形碰撞的游戏
- 02用关节或弹簧连接刚体
- 03创建触发区域和碰撞规则
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yakoub-ai/ phaser4-gamedev --skill phaser-matter安装后在 Claude Code 中输入/phaser-matter确认已加载。
附带文件references/matter-api.md
详细介绍
Phaser 4 Matter 物理引擎
Matter.js 是 Phaser 的第二个内置物理引擎。当 Arcade Physics 无法满足需求时使用它,尤其适用于非矩形碰撞形状、真实物理约束或布娃娃式模拟。
Arcade 与 Matter:何时使用哪种
Arcade Physics(默认选择):
- 仅支持 AABB 碰撞:矩形和圆形
- 简单的碰撞箱,性能出色
- 使用
body.blocked.down检测是否接触地面 - 可满足 95% 的 2D 游戏需求
Matter Physics(有需要时使用):
- 凸多边形形状和复杂的凹形物体
- 复合物体(多个形状焊接在一起)
- 逼真的约束:铰链、弹簧、定距杆
- 布娃娃物理和可破坏物体
- 具备精确物理碰撞事件的传感器区域
**原则:**默认使用 Arcade。仅在需要非矩形碰撞形状或真实关节时切换到 Matter。
启用 Matter Physics
// In GameConfig:
const config: Phaser.Types.Core.GameConfig = {
physics: {
default: 'matter',
matter: {
gravity: { x: 0, y: 1 }, // normalized: 1 = earth-like downward pull
debug: false, // true renders wireframes — essential during development
},
},
};
开发期间设置 debug: true。Matter 的线框叠加层会准确显示每个物体的形状、应用的偏移量以及约束的连接位置。
创建物体
矩形(默认)
// matter.add.image creates a MatterImage (static by default if you call .setStatic)
const box = this.matter.add.image(x, y, 'box');
圆形
const ball = this.matter.add.image(x, y, 'ball').setCircle(radius);
// Optional: pass options as second arg to setCircle
const ball2 = this.matter.add.image(x, y, 'ball').setCircle(32, { restitution: 0.8 });
凸多边形
const hex = this.matter.add.image(x, y, 'hex')
.setBody({ type: 'polygon', sides: 6, radius: 30 });
自定义顶点(fromVertices)
// Vertices defined as array of {x,y} relative to body center
const verts = [{ x: 0, y: -30 }, { x: 20, y: 10 }, { x: -20, y: 10 }];
const arrow = this.matter.add.image(x, y, 'arrow')
.setBody({ type: 'fromVertices', verts: verts });
注意:Matter 会通过 poly-decomp 自动将凹多边形分解为凸多边形。确保顶点的顺序一致(顺时针或逆时针)。
复合物体(多个形状合为一个物体)
// Use matter.bodies to build parts, then merge into a compound
const Body = (this.matter as any).body as typeof MatterJS.Body;
const Bodies = (this.matter as any).bodies as typeof MatterJS.Bodies;
const torso = Bodies.rectangle(x, y, 30, 50);
const head = Bodies.circle(x, y - 40, 15);
const compound = Body.create({ parts: [torso, head] });
const player = this.matter.add.image(x, y, 'player').setExistingBody(compound);
静态物体(不可移动)
const ground = this.matter.add.image(x, y, 'ground').setStatic(true);
// Or create a static rectangle directly:
this.matter.add.rectangle(x, y, width, height, { isStatic: true });
物体属性
所有属性设置器都会返回 MatterGameObject,以便链式调用。
const body = this.matter.add.image(x, y, 'crate');
body.setFriction(0.1); // surface friction: 0 = ice, 1 = very sticky
body.setFrictionAir(0.05); // air resistance / velocity damping per step
body.setFrictionStatic(0.5); // static friction (resistance to starting motion)
body.setRestitution(0.8); // bounciness: 0 = no bounce, 1 = perfect elastic
body.setMass(5); // kilograms — affects momentum transfer
body.setDensity(0.002); // alternative to setMass; mass derived from area
body.setIgnoreGravity(true); // float independent of world gravity
body.setFixedRotation(); // prevent rotation (essential for player characters!)
body.setSensor(true); // passes through objects but still fires collision events
body.setVelocity(vx, vy); // teleport velocity (px/frame, not px/sec like Arcade)
body.setAngularVelocity(0.1); // spin speed in radians per step
**平台游戏的关键事项:**始终对玩家物体调用 setFixedRotation()。否则,胶囊形物体会在接触表面时滚动并倾倒。
空气摩擦参考值:0.01 = 轻飘飘/太空感,0.05 = 正常,0.15 = 沉重/迟缓。
施加力
// Apply force at body center (world coordinates)
// Force is a vector in Matter units (very small numbers — tune empirically)
this.matter.applyForce(body.body as MatterJS.Body, { x: 0.01, y: -0.05 });
// Apply force at an offset point (creates torque)
this.matter.applyForceFromPosition(body.body as MatterJS.Body,
{ x: body.x + 10, y: body.y },
{ x: 0, y: -0.03 }
);
对于冲量式移动(跳跃、击退),直接设置速度,而不是施加力:
body.setVelocity(0, -10); // instant upward jump
碰撞检测
Matter 在世界上使用全局碰撞事件,而不是逐物体回调。
// collisionstart fires once when two bodies begin touching
this.matter.world.on(
'collisionstart',
(event: Phaser.Physics.Matter.Events.CollisionStartEvent) => {
event.pairs.forEach(pair => {
const { bodyA, bodyB } = pair;
const objA = bodyA.gameObject as Phaser.Physics.Matter.Image | null;
const objB = bodyB.gameObject as Phaser.Physics.Matter.Image | null;
if (objA && objB) {
// Both bodies have Phaser game objects attached
console.log('Collision:', objA.texture.key, 'hit', objB.texture.key);
}
});
}
);
// collisionactive fires every step while bodies remain touching
this.matter.world.on('collisionactive', (event) => { /* ... */ });
// collisionend fires once when bodies separate
this.matter.world.on('collisionend', (event) => { /* ... */ });
复合物体:每个部件都是独立的 Matter 物体。子部件的 bodyA.gameObject 会是 null;只有复合根物体才有 gameObject 引用。
碰撞过滤(类别和掩码)
使用位掩码类别控制哪些物体会相互碰撞。
// Define categories as powers of 2 (bitmask flags)
const CAT_PLAYER = 0x0001;
const CAT_ENEMY = 0x0002;
const CAT_BULLET = 0x0004;
const CAT_WALL = 0x0008;
const CAT_PICKUP = 0x0010;
// Player: collides with enemies and walls; own bullets pass through
player.setCollisionCategory(CAT_PLAYER);
player.setCollidesWith([CAT_ENEMY, CAT_WALL]);
// Enemy: collides with player, walls, and bullets
enemy.setCollisionCategory(CAT_ENEMY);
enemy.setCollidesWith([CAT_PLAYER, CAT_BULLET, CAT_WALL]);
// Bullet: hits enemies and walls, not the player who fired it
bullet.setCollisionCategory(CAT_BULLET);
bullet.setCollidesWith([CAT_ENEMY, CAT_WALL]);
// Pickup (coin/health): only player can collect it
pickup.setCollisionCategory(CAT_PICKUP);
pickup.setCollidesWith([CAT_PLAYER]);
每个世界最多支持 32 个类别。对于无论掩码如何设置都绝不应相互碰撞的物体(例如,同一组中的所有敌人物体使用共享的负组 ID,以跳过自碰撞),请使用碰撞过滤器的 group 属性。
传感器(触发区域)
传感器可检测重叠,但不会产生物理响应,适用于检查点、伤害区域和仇恨范围。
// Sensor rectangle (static)
const zone = this.matter.add.rectangle(x, y, width, height, {
isSensor: true,
isStatic: true,
label: 'checkpoint-zone', // label helps identify bodies in collision events
});
// Detect entries
this.matter.world.on('collisionstart', (event) => {
event.pairs.forEach(pair => {
const labels = [pair.bodyA.label, pair.bodyB.label];
if (labels.includes('checkpoint-zone')) {
const other = pair.bodyA.label === 'checkpoint-zone' ? pair.bodyB : pair.bodyA;
if (other.gameObject) {
// Player entered the zone
}
}
});
});
约束(关节)
约束通过杆、弹簧或铰链连接两个物体(或将一个物体连接到固定的世界坐标点)。
// Distance constraint — rigid rod between two bodies
const rod = this.matter.add.constraint(
bodyA.body as MatterJS.Body,
bodyB.body as MatterJS.Body,
100, // target length (pixels)
0.9 // stiffness: 1 = perfectly rigid, 0.01 = very springy
);
// Spring constraint — same but low stiffness
const spring = this.matter.add.constraint(
bodyA.body as MatterJS.Body,
bodyB.body as MatterJS.Body,
80, // rest length
0.02 // stiffness (spring-like)
// Note: damping defaults to 0; set options.damping to prevent oscillation
);
// Pin constraint — hinge a body to a fixed world point
const hinge = this.matter.add.worldConstraint(
pendulum.body as MatterJS.Body,
0, // length 0 = pivot at the anchor point
1, // stiffness
{
pointA: { x: 400, y: 100 }, // fixed world position
pointB: { x: 0, y: -40 }, // offset on the body (pivot attachment)
}
);
约束选项
interface MatterConstraintConfig {
bodyA?: MatterJS.Body;
bodyB?: MatterJS.Body;
pointA?: { x: number; y: number }; // attachment offset on bodyA
pointB?: { x: number; y: number }; // attachment offset on bodyB
length?: number; // rest length; null = current distance
stiffness?: number; // 0.01–1
damping?: number; // 0–1, reduces oscillation; 0.1 is a good starting point
label?: string;
}
移除物体和约束
// Destroy a Matter game object (removes body from world automatically)
sprite.destroy();
// Remove a constraint
this.matter.world.removeConstraint(rod);
// Remove a raw MatterJS body (not a game object)
this.matter.world.remove(rawBody);
使用 Matter 制作平台游戏
Matter 平台游戏需要格外注意,因为 Matter 中不存在 body.blocked.down,应改用碰撞事件。
class Player extends Phaser.Physics.Matter.Sprite {
private onGround = false;
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene.matter.world, x, y, 'player');
scene.add.existing(this);
this.setFixedRotation(); // prevent capsule rolling
this.setFrictionAir(0.05);
this.setCollisionCategory(0x0001);
// Track ground contact via sensor at feet
const sensor = (scene.matter as any).bodies.rectangle(0, 20, 20, 4, { isSensor: true });
const compound = (scene.matter as any).body.create({
parts: [this.body, sensor],
});
this.setExistingBody(compound, true);
scene.matter.world.on('collisionstart', (ev: any) => {
ev.pairs.forEach((p: any) => {
if (p.bodyA === sensor || p.bodyB === sensor) this.onGround = true;
});
});
scene.matter.world.on('collisionend', (ev: any) => {
ev.pairs.forEach((p: any) => {
if (p.bodyA === sensor || p.bodyB === sensor) this.onGround = false;
});
});
}
jump(): void {
if (this.onGround) this.setVelocityY(-10);
}
}
世界配置
// Pause/resume simulation
this.matter.world.pause();
this.matter.world.resume();
// Change gravity at runtime
this.matter.world.setGravity(0, 2); // doubled gravity
this.matter.world.setGravity(0, 0); // zero-g
// Run one manual step (useful for deterministic replays)
this.matter.world.step(16.666); // ms per step
// Set world bounds (creates static boundary bodies)
this.matter.world.setBounds(0, 0, worldWidth, worldHeight);
调试渲染
// Enable debug wireframes in config (recommended during development):
matter: { debug: true }
// Or toggle at runtime:
this.matter.world.drawDebug = !this.matter.world.drawDebug;
// Re-create the debug renderer if toggling on after init:
this.matter.world.createDebugGraphic();
其他资源
参考文件
references/matter-api.md— 完整的 MatterPhysics 插件方法、MatterGameObject 混入、MatterJS.Body 静态方法、约束选项、碰撞事件结构、CollisionFilter 接口