Matter.js Variable Spring Plunger Mechanism

This article explains how to build a pinball-style plunger launch mechanism with variable spring compression in Matter.js. You will learn how to configure the physics bodies, attach an elastic constraint to act as a spring, control pull-back distance through user input, and release accumulated potential energy to launch a ball with varying force.

1. Conceptual Overview

A functional variable plunger consists of three primary components:

2. Setting Up the Bodies and Constraint

Define the plunger head as a dynamic rectangle and anchor it using an elastic constraint. An additional static stopper body prevents the plunger from flying past its resting position into the playfield.

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

// Create engine and world
const engine = Engine.create();
const world = engine.world;

// Resting coordinates
const restX = 300;
const restY = 500;

// Plunger body
const plunger = Bodies.rectangle(restX, restY, 40, 20, {
    density: 0.05,
    frictionAir: 0.001,
    restitution: 0
});

// Fixed anchor behind the plunger
const anchor = { x: restX, y: restY + 100 };

// Spring constraint
const spring = Constraint.create({
    pointA: anchor,
    bodyB: plunger,
    pointB: { x: 0, y: 0 },
    stiffness: 0.08,
    damping: 0.02,
    render: {
        visible: true,
        lineWidth: 4,
        strokeStyle: '#ff5722'
    }
});

// Mechanical stops to limit movement
const topStopper = Bodies.rectangle(restX, restY - 5, 60, 5, {
    isStatic: true,
    isSensor: true // Set to false if you want hard collision instead of scripted limitation
});

Composite.add(world, [plunger, spring, topStopper]);

3. Implementing Variable Compression Logic

To achieve variable compression, track the duration of a key press (such as the spacebar or down arrow). As the key is held, apply a continuous displacement downward until a maximum compression distance is reached.

let isCompressing = false;
const maxCompression = 80; // Maximum pixels the spring can be pulled back
const pullSpeed = 2;       // Distance pulled per frame

window.addEventListener('keydown', (event) => {
    if (event.code === 'Space') {
        isCompressing = true;
    }
});

window.addEventListener('keyup', (event) => {
    if (event.code === 'Space') {
        isCompressing = false;
    }
});

4. Updating and Releasing the Plunger

Use Matter.js's beforeUpdate event to handle the pull-back and axis-locking logic. While the compression key is active, smoothly shift the plunger down. When released, the physics engine takes over, contracting the spring and firing the plunger upward.

Matter.Events.on(engine, 'beforeUpdate', () => {
    // Keep plunger locked strictly to the vertical axis
    Body.setPosition(plunger, { x: restX, y: plunger.position.y });
    Body.setVelocity(plunger, { x: 0, y: plunger.velocity.y });
    Body.setAngle(plunger, 0);
    Body.setAngularVelocity(plunger, 0);

    if (isCompressing) {
        // Compress the spring by moving the plunger backward
        if (plunger.position.y < restY + maxCompression) {
            Body.setPosition(plunger, {
                x: restX,
                y: plunger.position.y + pullSpeed
            });
            Body.setVelocity(plunger, { x: 0, y: 0 });
        }
    } else {
        // Prevent the spring from launching the plunger beyond its resting position
        if (plunger.position.y < restY) {
            Body.setPosition(plunger, { x: restX, y: restY });
            Body.setVelocity(plunger, { x: 0, y: 0 });
        }
    }
});

5. Transferring Momentum to the Ball

Place a dynamic ball resting directly on top of the plunger head:

const ball = Bodies.circle(restX, restY - 20, 15, {
    density: 0.004,
    restitution: 0.2
});

Composite.add(world, ball);

When the plunger is pulled back and released, the stored tension in the constraint snaps the plunger upward to restY, transferring kinetic energy directly to the ball upon collision before the plunger stops at its rest boundary. Longer hold times yield greater displacement, delivering higher launch velocities.