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:

  1. Filter the world bodies to find active targets labeled 'pullable'.
  2. Compute the vector distance from the target to the ship.
  3. Check if the target is within the maximum effective range.
  4. Normalize the direction vector and scale it by the desired pull force.
  5. 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