How to Simulate Air Resistance in Matter.js
Simulating air resistance in Matter.js can be achieved either by
utilizing the engine's built-in linear drag property or by calculating
and applying realistic quadratic aerodynamic forces manually. While the
built-in frictionAir property provides an easy and
lightweight way to slow bodies down over time, writing a custom drag
function via the engine's update events allows for accurate physical
simulations where drag scales with the square of velocity.
Method 1: Using Built-In
frictionAir
Matter.js bodies have a native property called
frictionAir. By default, this value is set to
0.01. Increasing this value will make an object slow down
faster in both linear and angular motion, simulating resistance against
an atmosphere.
You can set this property when creating the body:
const body = Matter.Bodies.circle(x, y, radius, {
frictionAir: 0.05 // Higher value creates stronger resistance
});You can also update it dynamically at runtime:
body.frictionAir = 0.08;Limitation: The built-in frictionAir
applies a linear deceleration proportional directly to the velocity
(\(F \propto v\)). In real-world
physics, air resistance is quadratic (\(F
\propto v^2\)), meaning high-speed objects experience drastically
more resistance than low-speed objects.
Method 2: Applying Realistic Quadratic Drag
To simulate realistic aerodynamic drag, set
frictionAir: 0 on the body and apply a counter-force
proportional to the square of its speed on each physics step using the
beforeUpdate event.
The formula for aerodynamic drag magnitude is:
\[F_d = \frac{1}{2} \rho v^2 C_d A\]
In game development, this is often simplified into a single drag coefficient \(k\):
\[\vec{F}_d = -k \cdot |\vec{v}| \cdot \vec{v}\]
Here is how to implement this in Matter.js:
const { Engine, Events, Body, Vector } = Matter;
// 1. Create a body with no native air friction
const projectile = Matter.Bodies.circle(100, 300, 20, {
frictionAir: 0
});
Matter.Composite.add(world, projectile);
// 2. Define your drag coefficient
const dragCoefficient = 0.005;
// 3. Apply custom drag force before each engine update
Events.on(engine, 'beforeUpdate', () => {
const velocity = projectile.velocity;
const speed = Vector.magnitude(velocity);
if (speed > 0) {
// Calculate drag force magnitude: F = k * v^2
const dragMagnitude = dragCoefficient * speed * speed;
// Calculate unit vector in the opposite direction of motion
const dragDirection = Vector.negate(Vector.normalise(velocity));
// Scale the direction by magnitude
const dragForce = Vector.mult(dragDirection, dragMagnitude);
// Apply force to the center of the body
Body.applyForce(projectile, projectile.position, dragForce);
}
});Method 3: Global Air Resistance for Multiple Bodies
If you want air resistance to affect all dynamic bodies in the
simulation, iterate over the composite's bodies inside the
beforeUpdate listener:
Events.on(engine, 'beforeUpdate', () => {
const bodies = Matter.Composite.allBodies(engine.world);
const globalDrag = 0.002;
bodies.forEach(body => {
if (body.isStatic || body.isSleeping) return;
const speed = Vector.magnitude(body.velocity);
if (speed > 0) {
const dragMagnitude = globalDrag * speed * speed;
const dragForce = Vector.mult(
Vector.negate(Vector.normalise(body.velocity)),
dragMagnitude
);
Body.applyForce(body, body.position, dragForce);
}
});
});Choosing the Right Approach
- Use
frictionAirif you need a quick, performant solution for top-down games, floating UI elements, or simple arcade mechanics. - Use Custom Forces if you are simulating ballistics, parachutes, falling objects reaching terminal velocity, or any scenario where high-speed movement requires non-linear atmospheric braking.