How to Create a Tractor Beam in Matter.js
This guide explains how to implement a functional tractor beam mechanic in Matter.js by tagging specific target bodies and continuously applying an attractive directional force toward a central ship using the engine's event loop and 2D vector mathematics.
1. Tagging the Bodies
Matter.js bodies can hold custom data using the label
property or a custom object. To ensure the tractor beam only affects
valid targets, assign an identifier when instantiating the bodies.
// The ship emitting the beam
const ship = Matter.Bodies.polygon(400, 300, 3, 30, {
label: 'ship',
isStatic: true // or dynamic depending on control scheme
});
// A target body that can be pulled
const ore = Matter.Bodies.circle(200, 150, 15, {
label: 'pullable',
density: 0.001
});
// A body that should be ignored by the beam
const asteroid = Matter.Bodies.circle(600, 200, 40, {
label: 'neutral'
});
Matter.Composite.add(engine.world, [ship, ore, asteroid]);2. Setting Up the Update Loop
Tractor beams require continuous application of force. Listen to the
beforeUpdate event on the Matter.js Engine to
compute and apply forces on every simulation step before physics
calculations resolve.
Matter.Events.on(engine, 'beforeUpdate', () => {
applyTractorBeam(ship, engine.world.bodies, {
maxRange: 350,
forceMagnitude: 0.0005
});
});3. Calculating and Applying the Attraction Force
To pull bodies toward the ship:
- Filter the world bodies to find active targets labeled
'pullable'. - Compute the vector distance from the target to the ship.
- Check if the target is within the maximum effective range.
- Normalize the direction vector and scale it by the desired pull force.
- Apply the force using
Matter.Body.applyForce().
function applyTractorBeam(sourceBody, bodies, options) {
const { maxRange, forceMagnitude } = options;
const sourcePos = sourceBody.position;
for (let i = 0; i < bodies.length; i++) {
const targetBody = bodies[i];
// Filter only tagged bodies and ignore the source itself
if (targetBody.label !== 'pullable' || targetBody === sourceBody) {
continue;
}
const targetPos = targetBody.position;
// Calculate displacement vector from target to ship
const deltaX = sourcePos.x - targetPos.x;
const deltaY = sourcePos.y - targetPos.y;
const distance = Math.hypot(deltaX, deltaY);
// Only apply force within maximum range and prevent division by zero
if (distance < maxRange && distance > 10) {
// Normalize vector
const normalX = deltaX / distance;
const normalY = deltaY / distance;
// Calculate force (optionally scale by distance to increase/decrease pull)
const force = {
x: normalX * forceMagnitude,
y: normalY * forceMagnitude
};
// Apply the force directly to the center of mass
Matter.Body.applyForce(targetBody, targetPos, force);
}
}
}4. Optional Enhancements
- Linear Damping: If the target body overshoots the
ship and orbits erratically, increase
frictionAir(e.g.,targetBody.frictionAir = 0.05) while it is under the beam's influence to dampen high velocities. - Angular Direction: To restrict the tractor beam to a forward-facing cone rather than an omnidirectional radius, check the angle between the ship's heading vector and the displacement vector using the dot product before applying force.