Build a Catapult with Matter.js Constraints

This guide demonstrates how to build a functional catapult in the Matter.js 2D physics engine by leveraging the Constraint module. You will learn the mechanics behind setting up a rotating lever arm, anchoring it to a fixed fulcrum point using a pin constraint, placing a projectile, and applying a trigger force or counterweight to launch objects across the physics world.

Understanding the Catapult Architecture

A basic catapult requires three fundamental components in Matter.js:

  1. The Lever Arm: A dynamic rectangular body that serves as the throwing arm.
  2. The Fulcrum Constraint: A point constraint that pins the lever arm to a fixed coordinate in the world, allowing it to pivot freely like a seesaw.
  3. The Payload and Trigger: A projectile placed on one end of the arm, and either a heavy counterweight, a falling body, or an applied force at the opposite end to initiate the launch.

Step 1: Initialize the Engine and World

First, import the required Matter.js modules and initialize your engine, renderer, and world:

const { Engine, Render, Runner, Bodies, Composite, Constraint, Vector } = Matter;

const engine = Engine.create();
const world = engine.world;

const render = Render.create({
  element: document.body,
  engine: engine,
  options: {
    width: 800,
    height: 600,
    wireframes: false
  }
});

Render.run(render);
Runner.run(Runner.create(), engine);

Step 2: Create the Lever Arm

Create a dynamic rectangle to represent the catapult beam. Give it enough length to provide leverage:

const armX = 400;
const armY = 450;
const armWidth = 320;
const armHeight = 20;

const catapultArm = Bodies.rectangle(armX, armY, armWidth, armHeight, {
  collisionFilter: { group: -1 }, // Prevent unwanted self-collisions
  density: 0.005
});

Step 3: Create the Pivot Constraint

To turn the rectangle into a lever, anchor it to a static coordinate using Constraint.create(). Setting pointB to the world position of the pivot and pointA to the arm's local pivot offset creates a revolving joint.

const fulcrum = Constraint.create({
  pointA: { x: 0, y: 0 }, // Pivot point relative to catapultArm center
  bodyB: catapultArm,
  pointB: { x: 0, y: 0 },
  stiffness: 1,
  length: 0,
  render: {
    visible: true,
    lineWidth: 5,
    strokeStyle: '#888888'
  }
});

If you want an off-center pivot to achieve greater leverage, offset pointA.x (e.g., { x: -40, y: 0 }).


Step 4: Add Stops and Bases

Without a stop, the arm can swing through full 360-degree rotations. Add static blocks below the arm to define the resting angle and the stopping angle:

const leftStop = Bodies.rectangle(260, 480, 40, 60, { isStatic: true });
const rightStop = Bodies.rectangle(540, 480, 40, 60, { isStatic: true });

Step 5: Add the Projectile and Trigger

Place a projectile on the launching end of the arm, and create a heavy body to drop onto the opposite end to act as the triggering mechanism:

// The projectile on the right side
const projectile = Bodies.circle(520, 410, 18, {
  density: 0.004,
  render: { fillStyle: '#e74c3c' }
});

// A heavy dropping weight positioned above the left side
const counterweight = Bodies.rectangle(260, 150, 50, 50, {
  density: 0.05, // High density creates substantial downward force
  render: { fillStyle: '#34495e' }
});

Step 6: Add All Elements to the World

Finally, add all created bodies and constraints to the engine's composite:

Composite.add(world, [
  catapultArm,
  fulcrum,
  leftStop,
  rightStop,
  projectile,
  counterweight
]);

When the physics simulation runs, the heavy counterweight falls due to gravity, strikes the left side of the catapult arm, and rotates the lever around the fulcrum constraint, propelling the projectile from the right end into the air.