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:
- The Lever Arm: A dynamic rectangular body that serves as the throwing arm.
- 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.
- 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.