Simulating a Slingshot with Matter.js Elastic Bands

This article covers how to build an interactive slingshot mechanic using Matter.js, a 2D physics engine for the web. You will learn how to configure the physics engine, create an elastic band using spring constraints, attach a projectile, handle mouse dragging interactions, and programmatically release the projectile to launch it through 2D space.

Core Mechanics Overview

A functional slingshot simulation in Matter.js requires four primary components:

  1. Anchor Point: A static reference point where the elastic band is anchored.
  2. Projectile: A dynamic rigid body that the player pulls and launches.
  3. Elastic Constraint: A Matter.js Constraint acting as the elastic band, defined with specific stiffness and damping values.
  4. Release Logic: An event listener that detects when the user releases the projectile and detaches the constraint at the right moment.

Step 1: Setting Up the Physics Engine and World

Initialize the standard Matter.js modules: Engine, Render, Runner, Bodies, Composite, Constraint, Mouse, and MouseConstraint.

const { Engine, Render, Runner, Bodies, Composite, Constraint, Mouse, MouseConstraint, Events } = 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: Creating the Projectile and Elastic Constraint

Create a dynamic circle body for the projectile. Place the anchor point at the slingshot's rest position, then connect the projectile to this anchor using a Constraint.

const anchor = { x: 200, y: 400 };

// Create the projectile
const projectile = Bodies.circle(anchor.x, anchor.y, 20, {
  density: 0.004,
  restitution: 0.8,
  render: { fillStyle: '#e74c3c' }
});

// Create the elastic band constraint
const elastic = Constraint.create({
  pointA: anchor,
  bodyB: projectile,
  stiffness: 0.05,
  damping: 0.01,
  render: {
    strokeStyle: '#e67e22',
    lineWidth: 5
  }
});

Composite.add(world, [projectile, elastic]);

Step 3: Enabling Mouse Dragging

Add a MouseConstraint so players can click and pull the projectile backward.

const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: { visible: false }
  }
});

Composite.add(world, mouseConstraint);
render.mouse = mouse;

Step 4: Coding the Launch Release Logic

A common slingshot mechanic releases the projectile when the user lets go of the mouse button while dragging. Using Matter.js events, check when dragging ends (enddrag), determine if the released body is the projectile, and detach the elastic constraint once it springs past or reaches the anchor position.

let isFired = false;

Events.on(mouseConstraint, 'enddrag', (event) => {
  if (event.body === projectile) {
    isFired = true;
  }
});

Events.on(engine, 'afterUpdate', () => {
  if (isFired) {
    // Check if the projectile has moved forward past the anchor point
    const distanceX = projectile.position.x - anchor.x;
    
    // Release the projectile when it crosses forward or snaps back close to the anchor
    if (distanceX > 0 || projectile.speed > 20) {
      Composite.remove(world, elastic);
      isFired = false;
    }
  }
});

Step 5: Reloading the Slingshot

To allow successive shots, spawn a new projectile and reconnect the constraint after the previous projectile moves out of view or comes to rest:

function reloadSlingshot() {
  const newProjectile = Bodies.circle(anchor.x, anchor.y, 20, {
    density: 0.004,
    restitution: 0.8,
    render: { fillStyle: '#e74c3c' }
  });

  elastic.bodyB = newProjectile;

  Composite.add(world, [newProjectile, elastic]);
}

This completes the slingshot mechanic. Adjusting the stiffness and density parameters will allow you to calibrate shot velocity, band tension, and projectile weight to fit your game design.