I spent some time playing with Matter.js, a JavaScript-powered 2D physics engine. Overall, pretty fun stuff. This article shares some highlights from my time spent with Matter.js, but I wouldn’t consider it a from-the-ground-up tutorial. If that’s what you’re looking for, the official Wiki is a good starting point. There’s also this tutorial series that I found useful.
Getting Started
My first Matter.js project was mostly just an excuse to throw a bunch of bodies around and see the physics in action. Here it is.
See the Pen Browser Crash by Will Boyd (@lonekorean) on CodePen.
There is a bit of setup to do before you can start adding bodies and tossing them around. Don’t worry, it’s not so bad — you can mostly just treat it as boilerplate code and add in whatever options you need. Here’s a slightly modified version of the setup code used in the demo above.
// engine
let engine = Matter.Engine.create();
// render
let render = Matter.Render.create({
element: document.getElementById('container'),
engine: engine,
options: {
width: SOME_WIDTH,
height: SOME_HEIGHT,
wireframes: false
}
});
Matter.Render.run(render);
// runner
let runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);
// now you can start adding bodies...
After that, adding bodies is easy.
// params: x, y, width, height, options
let square = Matter.Bodies.rectangle(200, 100, 50, 50, {
// specify options here
});
Matter.World.add(engine.world, square);
You can add all kinds of shapes, even arbitrary polygons (more on this later).
Working with Functions
I found myself repeatedly adding similar shapes with the same set of options, which led me to abstract things out to a handful of body creation functions. This made adding bodies more manageable. For example, I used the following function to create all the rectangular bodies.
function rect(x, y, width, height, color) {
return Matter.Bodies.rectangle(x, y, width, height, {
isStatic: true,
restitution: 1,
render: { fillStyle: color }
});
}
Overall, I found the syntax of Matter.js easy to work with. Maybe the one thing that took some getting used to was its affinity for passing bodies into functions, instead of calling methods directly on the bodies.
// wrong
square.setAngularVelocity(0.1);
// correct
Matter.Body.setAngularVelocity(square, 0.1);
Rounded Shapes
Next up, I wanted to make something that required more deliberate mechanics than just “throwing stuff around”. Pinball sounded like fun. Click/tap (and hold) the buttons to control the paddles, or use your left/right arrow keys.
See the Pen Pinball Physics by Will Boyd (@lonekorean) on CodePen.
Creating the pieces for a pinball table required some finesse to make sure the pinball rolled off objects properly. For starters, all the cyan bodies have rounded corners. This is pretty easy to do with the chamfer
option.
function wall(x, y, width, height) {
return Matter.Bodies.rectangle(x, y, width, height, {
chamfer: { radius: 10 },
// other options here...
});
}
You can actually just set chamfer
to an empty literal {}
and get some default corner rounding, but specifying a radius
value gives you more control. It’s worth noting that chamfer
isn’t just for looks — it actually does affect collision detection.
Complex Shapes
Some body shapes were a bit more complex to define — most notably the “dome” at the top of the pinball table. The general idea is to create an array of x/y coordinates to feed into Matter.Bodies.fromVertices()
. Matter.js can then use decomp.js behind the scenes to bring complex body shapes to life (side note: you have to add decomp.js yourself).
While it’s possible to manually craft the coordinates for simple shapes, it quickly becomes unreasonable for more complex shapes. For example, the dome has 20 edges just to create its curved underside. Fortunately, Illustrator can do this for you. Here are the steps.
- Create a new document and draw your singular shape. Make sure it has no stroke and a simple fill color (black will do).
- Go to File > Export > Export As…
- Change type to SVG and click Export.
- In the SVG Options modal, click Show Code (the settings here generally don’t matter).
- A text file will open up containing what you need. Success!
In the text file, you’ll see a tag that looks something like this.
<polygon points="425.6 327 273.8 315.6..."/>
That (potentially long) string of space-separated numbers is what you want. Now you can use it in code to create your shape.
const CUSTOM_PATH = '425.6 327 273.8 315.6...';
function customShape(x, y) {
let vertices = Matter.Vertices.fromPath(CUSTOM_PATH);
return Matter.Bodies.fromVertices(x, y, vertices, {
// set options if you need them...
});
}
Collision Detection Issues
Matter.js doesn’t have continuous collision detection. The problem is described here, but basically, if a body is traveling fast enough towards another body, the engine may not register that a collision should occur if that other body is too thin. This was a problem when the pinball would fly towards a paddle and pass right through it.
My solution was to use a composite body to attach an invisible thick “brick” to each paddle. The extra thickness made collision detection reliable.
Paddle Mechanics
Creating the hinge for the paddle was relatively easy to do with a constraint. Check out this demo to see another hinge constraint in action.
The tricky part was getting the paddle movement right. It needed to have a confined range of movement with hard stops for both the up and down positions. Ultimately, I settled on a solution using the matter-attractors plugin.
The 4 large circles revealed above (but not visible in the demo) are “stoppers”. They constrict the movement of the paddles, but allow the pinball to pass right through thanks to collision filters. They are also set up to act as paddle attractors (think magnets), using that plugin I mentioned. To swing a paddle up, the top stopper’s attracting force is turned on. Likewise, the bottom stopper’s attracting force is turned on to pull the paddle down.
Events
Matter.js exposes a number events on various objects that you can hook into. For example, Matter.Engine
fires a collisionStart
event whenever bodies collide.
Matter.Events.on(engine, 'collisionStart', function(event) {
let a = event.pairs.bodyA;
let b = event.pairs.bodyB;
// check bodies, do whatever...
});
The obvious usage here is detecting when the pinball hits a bumper, so I can flash the bumper color and increment the score.
As another example, I’m using the beforeUpdate
event on Matter.Engine
as a cheap way to keep the pinball from falling back down the shooter lane. If the pinball is far enough right (so it’s in shooter lane) and has a downward velocity, then I manually set an upward velocity to fire the pinball up and out.
Matter.Events.on(engine, 'beforeUpdate', function(event) {
if (pinball.position.x > 450 && pinball.velocity.y > 0) {
Matter.Body.setVelocity(pinball, { x: 0, y: -10 });
}
});
Final Thoughts
Matter.js is a lot of fun as a playground. It’s fairly easy to get a whole slew of impressive physics-based interactions going, as you can see from this collection of demos. There’s an obvious appeal to using Matter.js for physics-based games. I’ve also seen cases of Matter.js being used to add visual flair to a website, especially with reactive backgrounds. Check this gallery for examples.
The pinball project was much tougher than I anticipated. I learned that the real-life physics of a pinball table are very nuanced and difficult to fully capture in code.
If anything in this article interests you, then I encourage you to give Matter.js a try. If nothing else, it’s just fun to play with.