| name | physics-matter |
| description | Use this skill when using Matter.js physics in Phaser 4. Covers rigid bodies, constraints, composite bodies, sensors, collision filtering, world configuration, and advanced physics shapes. Triggers on: Matter, matter physics, constraint, joint, rigid body, sensor. |
Matter.js Physics
Setting up and using Matter.js physics in Phaser 4 -- full-body physics with rigid bodies, compound bodies, constraints, composites, sensors, collision filtering, pointer dragging, tilemap integration, and debug rendering.
Key source paths: src/physics/matter-js/MatterPhysics.js, src/physics/matter-js/World.js, src/physics/matter-js/Factory.js, src/physics/matter-js/MatterSprite.js, src/physics/matter-js/MatterImage.js, src/physics/matter-js/MatterGameObject.js, src/physics/matter-js/PointerConstraint.js, src/physics/matter-js/MatterTileBody.js, src/physics/matter-js/components/, src/physics/matter-js/events/, src/physics/matter-js/typedefs/
Related skills: ../game-setup-and-config/SKILL.md, ../sprites-and-images/SKILL.md, ../physics-arcade/SKILL.md, ../tilemaps/SKILL.md
Quick Start
class GameScene extends Phaser.Scene {
create() {
this.player = this.matter.add.sprite(400, 200, 'player');
this.player.setBounce(0.5);
this.player.setFriction(0.05);
const box = this.matter.add.image(300, 100, 'crate');
this.matter.add.rectangle(400, 580, 800, 40, { isStatic: true });
this.matter.add.mouseSpring();
this.cursors = this.input.keyboard.createCursorKeys();
}
update() {
if (this.cursors.left.isDown) {
this.player.setVelocityX(-5);
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(5);
}
if (this.cursors.up.isDown && this.player.body.velocity.y > -0.1) {
this.player.setVelocityY(-10);
}
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'matter',
matter: {
gravity: { y: 1 },
enableSleeping: true,
debug: true,
setBounds: true
}
},
scene: GameScene
};
const game = new Phaser.Game(config);
Core Concepts
Scene Plugin (this.matter)
The MatterPhysics class is the scene-level plugin. Key properties:
this.matter.add -- Factory for creating bodies, constraints, Game Objects (auto-added to world).
this.matter.world -- World instance managing the engine, bounds, debug rendering. Extends EventEmitter.
this.matter.body / bodies / composite / composites / constraint -- Direct references to Matter.js modules for low-level use.
this.matter.bodyBounds -- Helper for aligning bodies by visual bounds.
World (this.matter.world)
engine -- The MatterJS.Engine instance.
localWorld -- The MatterJS.World composite containing all bodies and constraints.
enabled -- Boolean; false pauses simulation. autoUpdate -- true = engine updates each game step.
walls -- { left, right, top, bottom } boundary wall bodies (or null).
World Config (MatterWorldConfig)
Passed under physics.matter in game or scene config:
| Property | Default | Purpose |
|---|
gravity | { x: 0, y: 1 } | Gravity vector. Set false to disable |
setBounds | false | true or { x, y, width, height, thickness, left, right, top, bottom } |
enableSleeping | false | Allow bodies to sleep when at rest |
positionIterations | 6 | Position solving accuracy |
velocityIterations | 4 | Velocity solving accuracy |
constraintIterations | 2 | Constraint stability |
timing.timeScale | 1 | 0 freezes, 0.5 slow-motion |
autoUpdate | true | Auto-step each game frame |
debug | false | true or MatterDebugConfig object |
runner | {} | Use runner.fps for fixed timestep |
Matter Game Objects
Phaser provides two physics-aware Game Object classes and a function to add physics to any Game Object:
Phaser.Physics.Matter.Sprite -- Extends Sprite with all Matter components. Created via this.matter.add.sprite(x, y, key, frame, options). Supports animations.
Phaser.Physics.Matter.Image -- Extends Image with all Matter components. Created via this.matter.add.image(x, y, key, frame, options). No animation support, lighter weight.
MatterGameObject(world, gameObject, options) -- Injects all Matter components into any existing Game Object. Created via this.matter.add.gameObject(mySprite, options).
Both MatterSprite and MatterImage default to a rectangle body matching the texture size. Pass options.shape to override.
Matter Components (Mixins)
All Matter Game Objects have these component methods mixed in:
| Component | Key Methods |
|---|
| Velocity | setVelocity(x, y), setVelocityX(x), setVelocityY(y), getVelocity(), setAngularVelocity(v), getAngularVelocity(), setAngularSpeed(s), getAngularSpeed() |
| Force | applyForce(vec2), applyForceFrom(position, force), thrust(speed), thrustLeft(speed), thrustRight(speed), thrustBack(speed) |
| Bounce | setBounce(value) -- restitution, 0 to 1 |
| Friction | setFriction(value, air?, fstatic?), setFrictionAir(value), setFrictionStatic(value) |
| Mass | setMass(value), setDensity(value), centerOfMass (getter) |
| Gravity | setIgnoreGravity(bool) |
| Sensor | setSensor(bool), isSensor() |
| Static | setStatic(bool), isStatic() |
| Sleep | setToSleep(), setAwake(), setSleepThreshold(n), setSleepEvents(start, end) |
| Collision | setCollisionCategory(cat), setCollisionGroup(group), setCollidesWith(cats), setOnCollide(cb), setOnCollideEnd(cb), setOnCollideActive(cb), setOnCollideWith(body, cb) |
| SetBody | setRectangle(w, h, opts), setCircle(r, opts), setPolygon(r, sides, opts), setTrapezoid(w, h, slope, opts), setBody(config, opts), setExistingBody(body) |
| Transform | Position sync between Matter body and Game Object |
Common Patterns
Setup and World Configuration
this.matter.world.setGravity(0, 2);
this.matter.world.disableGravity();
this.matter.world.setBounds(0, 0, 1600, 1200, 64, true, true, true, true);
this.matter.set60Hz();
this.matter.world.autoUpdate = false;
this.matter.pause();
this.matter.resume();
Creating Matter Sprites and Images
const player = this.matter.add.sprite(200, 300, 'hero');
const ball = this.matter.add.image(400, 100, 'ball', null, { shape: { type: 'circle', radius: 24 } });
const hex = this.matter.add.sprite(300, 100, 'hex', null, { shape: { type: 'polygon', sides: 6, radius: 32 } });
const ship = this.matter.add.sprite(400, 200, 'ship', null, {
shape: { type: 'fromVerts', verts: '0 0 40 0 40 40 20 60 0 40' }
});
const shapes = this.cache.json.get('shapes');
const enemy = this.matter.add.sprite(500, 200, 'enemy', null, { shape: shapes.enemy });
const existingSprite = this.add.sprite(100, 100, 'box');
this.matter.add.gameObject(existingSprite, { restitution: 0.8 });
Body Configuration Options (MatterBodyConfig)
Pass as the options parameter to any factory method or as the options for a Matter Game Object:
label, isStatic, isSensor, angle (radians), timeScale, ignoreGravity, ignorePointer
density (0.001 default, auto-calculates mass), mass, restitution (bounce 0-1)
friction (0-1), frictionAir (air resistance), frictionStatic (stickiness when still)
slop (overlap tolerance), chamfer ({ radius: 5 } for rounded corners)
collisionFilter: { category: 0x0001, mask: 0xFFFFFFFF, group: 0 }
onCollideCallback, onCollideEndCallback, onCollideActiveCallback
shape (for Game Objects): { type: 'circle', radius: 24 } or PhysicsEditor data
Velocity, Forces, and Thrust
sprite.setVelocity(3, -5);
sprite.setVelocityX(-3);
sprite.setAngularVelocity(0.05);
const vel = sprite.getVelocity();
sprite.applyForce({ x: 0.05, y: 0 });
sprite.applyForceFrom(position, { x: 0.02, y: -0.02 });
sprite.thrust(0.05);
sprite.thrustBack(0.05);
sprite.thrustLeft(0.03);
sprite.thrustRight(0.03);
this.matter.setVelocity(arrayOfBodies, 2, -3);
this.matter.applyForce(arrayOfBodies, { x: 0.01, y: 0 });
Constraints (Joints and Springs)
const rigid = this.matter.add.constraint(bodyA, bodyB, 100, 1);
const spring = this.matter.add.constraint(bodyA, bodyB, 200, 0.02, { damping: 0.05 });
const pin = this.matter.add.constraint(bodyA, bodyB, 0, 0.9);
this.matter.add.worldConstraint(body, 50, 0.5, { pointA: { x: 400, y: 100 } });
this.matter.add.constraint(bodyA, bodyB, 80, 1, {
pointA: { x: 20, y: 0 },
pointB: { x: -20, y: 0 }
});
this.matter.getConstraintLength(constraint);
this.matter.world.removeConstraint(constraint);
Composites (Stacks, Chains, Soft Bodies)
const stack = this.matter.add.stack(100, 100, 5, 4, 10, 10, (x, y) => {
return this.matter.bodies.rectangle(x, y, 40, 40);
});
const imageStack = this.matter.add.imageStack('crate', null, 100, 100, 5, 4, 5, 5);
this.matter.add.chain(stack, 0.5, 0, -0.5, 0, { stiffness: 0.7 });
this.matter.add.mesh(stack, 5, 4, true, { stiffness: 0.5 });
this.matter.add.softBody(200, 100, 5, 5, 0, 0, true, 10, { friction: 0.1 }, { stiffness: 0.5 });
this.matter.add.newtonsCradle(300, 50, 5, 20, 200);
this.matter.add.car(400, 300, 120, 30, 25);
Compound Bodies
Combine multiple shapes into a single body. The first part is the parent.
const partA = this.matter.bodies.rectangle(0, 0, 60, 20);
const partB = this.matter.bodies.circle(0, -30, 15);
const compoundBody = this.matter.body.create({ parts: [partA, partB] });
const player = this.matter.add.sprite(400, 200, 'hero');
player.setExistingBody(compoundBody);
Parts share position, angle, and velocity. Constraints must target the parent body, not parts.
Sleep System
Bodies at rest can sleep to skip simulation. Requires enableSleeping: true in config.
if (body.isSleeping) { }
sprite.setSleepThreshold(30);
sprite.setToSleep();
sprite.setAwake();
sprite.setSleepEvents(true, true);
this.matter.world.on('sleepstart', (event, body) => { });
this.matter.world.on('sleepend', (event, body) => { });
Sensors
Sensors detect collisions but do not physically react. Useful for trigger zones, pickups, detection areas.
const trigger = this.matter.add.rectangle(400, 300, 100, 100, { isSensor: true, isStatic: true });
sprite.setSensor(true);
sprite.isSensor();
Collision Categories and Filtering
Matter uses bitmasks: category (which group this body belongs to, power of 2), mask (which categories it collides with), and group (shortcut: same positive = always collide, same negative = never collide, 0 = use category/mask).
const PLAYER = this.matter.world.nextCategory();
const ENEMY = this.matter.world.nextCategory();
const GROUND = this.matter.world.nextCategory();
player.setCollisionCategory(PLAYER);
player.setCollidesWith([ENEMY, GROUND]);
bullet.setCollisionCategory(0x0010);
bullet.setCollidesWith([ENEMY, GROUND]);
const noCollide = this.matter.world.nextGroup(true);
spriteA.setCollisionGroup(noCollide);
spriteB.setCollisionGroup(noCollide);
Collision Callbacks
player.setOnCollide((pair) => { });
player.setOnCollideEnd((pair) => { });
player.setOnCollideActive((pair) => { });
player.setOnCollideWith(enemy, (body, pair) => { });
player.on('collide', (bodyA, bodyB, pair) => {});
player.on('collideEnd', (bodyA, bodyB, pair) => {});
Tilemap Integration
const map = this.make.tilemap({ key: 'level' });
const tileset = map.addTilesetImage('tiles', 'tiles-img');
const layer = map.createLayer('Ground', tileset, 0, 0);
layer.setCollisionByProperty({ collides: true });
this.matter.world.convertTilemapLayer(layer);
Friction Types
Matter.js has three independent friction values:
sprite.setFriction(0.1);
sprite.setFrictionStatic(0.5);
sprite.setFrictionAir(0.05);
sprite.setFriction(0.1, 0.02, 0.3);
Complex Shapes from Vertices
const body = this.matter.add.fromVertices(400, 300, '0 0 40 0 40 40 20 60 0 40');
const vertexSets = [
[{ x: 0, y: 0 }, { x: 40, y: 0 }, { x: 40, y: 40 }],
[{ x: 40, y: 40 }, { x: 20, y: 60 }, { x: 0, y: 40 }]
];
this.matter.add.fromVertices(300, 200, vertexSets);
Pointer Constraint (Mouse/Touch Dragging)
const pc = this.matter.add.mouseSpring({ stiffness: 0.2, damping: 0.1 });
pc.active = false;
body.ignorePointer = true;
pc.stopDrag();
pc.destroy();
this.matter.world.on('dragstart', (body, part, constraint) => {});
this.matter.world.on('drag', (body, constraint) => {});
this.matter.world.on('dragend', (body, constraint) => {});
Queries (Raycasting and Hit Testing)
const hits = this.matter.intersectPoint(pointer.x, pointer.y);
const contains = this.matter.containsPoint(body, x, y);
const inRegion = this.matter.intersectRect(100, 100, 200, 200);
const rayHits = this.matter.intersectRay(0, 300, 800, 300, 1);
const colliding = this.matter.intersectBody(playerBody);
this.matter.overlap(playerBody, enemyBodies, (bodyA, bodyB, info) => {
console.log('Overlapping', bodyA, bodyB);
});
Debug Rendering
this.matter.world.drawDebug = false;
this.matter.world.debugGraphic.visible = false;
this.matter.world.setBodyRenderStyle(body, 0xff0000, 1, 2, 0x00ff00, 0.5);
this.matter.world.setConstraintRenderStyle(constraint, 0xffff00, 1, 2);
Events
All events are emitted on this.matter.world (which extends EventEmitter):
| Event | Callback Signature | When |
|---|
'collisionstart' | (event, bodyA, bodyB) | Two bodies first start colliding |
'collisionactive' | (event, bodyA, bodyB) | Two bodies are still colliding |
'collisionend' | (event, bodyA, bodyB) | Two bodies stop colliding |
'beforeupdate' | (event) | Before engine update step |
'afterupdate' | (event) | After engine update step |
'beforeadd' | (event) | Before a body/constraint is added |
'afteradd' | (event) | After a body/constraint is added |
'beforeremove' | (event) | Before a body/constraint is removed |
'afterremove' | (event) | After a body/constraint is removed |
'dragstart' | (body, part, constraint) | Pointer starts dragging body |
'drag' | (body, constraint) | Pointer is dragging body |
'dragend' | (body, constraint) | Pointer stops dragging body |
'sleepstart' | (event, body) | Body goes to sleep (requires setSleepEvents) |
'sleepend' | (event, body) | Body wakes up (requires setSleepEvents) |
'pause' | none | World paused |
'resume' | none | World resumed |
Collision events include event.pairs -- an array of collision pair objects with bodyA, bodyB, collision depth, and normal.
API Quick Reference
this.matter.add (Factory)
Game Objects: sprite(x, y, key, frame?, opts?), image(x, y, key, frame?, opts?), gameObject(go, opts?), tileBody(tile, opts?)
Body shapes: rectangle(x, y, w, h, opts?), circle(x, y, r, opts?), polygon(x, y, sides, r, opts?), trapezoid(x, y, w, h, slope, opts?), fromVertices(x, y, verts, opts?), fromPhysicsEditor(x, y, config, opts?), fromSVG(x, y, xml, scale?, opts?), fromJSON(x, y, config, opts?)
Constraints: constraint(a, b, len?, stiff?, opts?) (aliases: joint, spring), worldConstraint(body, len?, stiff?, opts?), mouseSpring(opts?) (alias: pointerConstraint)
Composites: stack(x, y, cols, rows, colGap, rowGap, cb), imageStack(key, frame, x, y, cols, rows), pyramid(...), chain(composite, xA, yA, xB, yB, opts?), mesh(composite, cols, rows, cross, opts?), softBody(...), car(x, y, w, h, wheelSize), newtonsCradle(x, y, num, size, len)
this.matter (MatterPhysics) -- batch and utility
pause(), resume(), set60Hz(), set30Hz(), step(delta?), setVelocity(bodies, x, y), setAngularVelocity(bodies, v), applyForce(bodies, force), applyForceFromAngle(bodies, speed, angle?), containsPoint(body, x, y), intersectPoint(x, y), intersectRect(x, y, w, h, outside?), intersectRay(x1, y1, x2, y2, width?), intersectBody(body), overlap(target, bodies?, cb?), setCollisionCategory(bodies, value), setCollisionGroup(bodies, value), setCollidesWith(bodies, cats), alignBody(body, x, y, align)
this.matter.world (World)
setBounds(x?, y?, w?, h?, thickness?, l?, r?, t?, b?), setGravity(x?, y?, scale?), disableGravity(), add(object), remove(object, deep?), removeConstraint(constraint), convertTilemapLayer(layer, opts?), convertTiles(tiles, opts?), nextCategory(), nextGroup(isNonColliding?), getAllBodies(), has(body), pause(), resume()
Direct Matter.js module references on this.matter
body (Matter.Body), bodies (Matter.Bodies), composite (Matter.Composite), composites (Matter.Composites), constraint (Matter.Constraint), detector, query, pair, pairs, resolver, axes, bounds, svg, vector, vertices
Gotchas
- Force values are tiny. Use
0.01-0.1 for forces, 1-15 for velocity. Not pixel-based.
setBody/setRectangle/etc. resets all properties -- mass, friction, collision filters, callbacks are wiped. Re-apply after changing shape.
- Constraints must target parent body, not compound body
parts.
- 32 collision categories max. Each
nextCategory() uses one bit.
collisionFilter.group overrides category/mask. Same positive = always collide; same negative = never collide; zero/different = use category/mask.
- Sensors still need matching collision filters to fire events.
- Matter position is center of mass, not top-left (unlike Arcade Physics).
- Sleep events require opt-in:
sprite.setSleepEvents(true, true).
- Tilemap conversion requires collision set first via
setCollisionByProperty etc.
- Restitution uses
Math.max(bodyA.restitution, bodyB.restitution) -- the bouncier value wins.
body.ignorePointer = true prevents pointer constraint from dragging that body.
Source File Map
| Path | Purpose |
|---|
src/physics/matter-js/MatterPhysics.js | Scene plugin (this.matter), exposes all Matter modules |
src/physics/matter-js/World.js | World management, engine, bounds, debug, events proxy |
src/physics/matter-js/Factory.js | this.matter.add -- all creation methods |
src/physics/matter-js/MatterSprite.js | Physics sprite (Sprite + Matter components) |
src/physics/matter-js/MatterImage.js | Physics image (Image + Matter components) |
src/physics/matter-js/MatterGameObject.js | Injects Matter components into any Game Object |
src/physics/matter-js/MatterTileBody.js | Wraps a Tile with a Matter body |
src/physics/matter-js/PointerConstraint.js | Click-and-drag body constraint |
src/physics/matter-js/BodyBounds.js | Body alignment by visual bounds |
src/physics/matter-js/PhysicsEditorParser.js | Parses PhysicsEditor JSON into bodies |
src/physics/matter-js/components/ | Mixins: Velocity, Force, Collision, SetBody, Sensor, Bounce, Friction, Mass, Gravity, Static, Sleep, Transform |
src/physics/matter-js/events/ | Event constants (COLLISION_START, DRAG_START, SLEEP_START, etc.) |
src/physics/matter-js/typedefs/ | TypeDefs: MatterWorldConfig, MatterBodyConfig, MatterCollisionFilter, MatterConstraintConfig, etc. |
src/physics/matter-js/lib/ | Bundled Matter.js library modules |