Building a Grappling Hook in Matter.js

This guide explains how to implement a functional grappling hook mechanic in Matter.js where a player shoots a cable at a wall and reels themselves in. You will learn how to detect wall collisions using raycasting, attach a dynamic distance constraint to simulate the cable, contract the constraint length to pull the player forward, and release the cable when desired.

1. Basic Setup

First, initialize your standard Matter.js engine, renderer, player body, and static environment obstacles (walls).

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

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

const player = Bodies.circle(100, 300, 20, { 
    density: 0.002, 
    frictionAir: 0.05 
});

const wall = Bodies.rectangle(600, 300, 50, 400, { 
    isStatic: true 
});

Composite.add(world, [player, wall]);

2. Finding the Attachment Point with Raycasting

When the player triggers the hook (for example, via mouse click), cast a ray from the player toward the target coordinates to find the nearest valid collision point on a static body.

let hookConstraint = null;

function fireHook(targetPosition) {
    if (hookConstraint) return; // Hook already active

    // Calculate ray trajectory
    const rayStart = player.position;
    const rayDirection = Vector.normalise(Vector.sub(targetPosition, rayStart));
    const maxDistance = 600;
    const rayEnd = Vector.add(rayStart, Vector.mult(rayDirection, maxDistance));

    // Get all static bodies in the world
    const bodies = Composite.allBodies(world).filter(b => b.isStatic);

    // Cast the ray
    const collisions = Query.ray(bodies, rayStart, rayEnd);

    if (collisions.length > 0) {
        // Find the closest collision point
        const hit = collisions[0];
        const hitPoint = hit.point;

        attachHook(hitPoint);
    }
}

3. Creating the Cable Constraint

Once a collision point is detected, connect the player to that static coordinate using a Matter.Constraint. The constraint acts as the physical cable. Set its initial length to the distance between the player and the hit point.

function attachHook(point) {
    const initialDistance = Vector.magnitude(Vector.sub(player.position, point));

    hookConstraint = Constraint.create({
        bodyA: player,
        pointB: point,
        length: initialDistance,
        stiffness: 0.05,  // Slight elasticity for realistic tension
        damping: 0.1,
        render: {
            strokeStyle: '#ffffff',
            lineWidth: 3
        }
    });

    Composite.add(world, hookConstraint);
}

4. Pulling the Player

To pull the player toward the anchor point, shorten the constraint's length on every physics update (beforeUpdate). Gradually decreasing the length forces the physics solver to pull the player toward the wall while preserving momentum.

const pullSpeed = 4; // Pixels per frame to reel in
const minDistance = 30; // Stop pulling once player is close

Matter.Events.on(engine, 'beforeUpdate', () => {
    if (!hookConstraint) return;

    if (hookConstraint.length > minDistance) {
        hookConstraint.length = Math.max(minDistance, hookConstraint.length - pullSpeed);
    }
});

5. Releasing the Hook

To disconnect the grapple and allow the player to fly off with their current momentum, remove the constraint from the world and nullify the reference.

function releaseHook() {
    if (hookConstraint) {
        Composite.remove(world, hookConstraint);
        hookConstraint = null;
    }
}

// Example input listeners
window.addEventListener('mousedown', (e) => {
    fireHook({ x: e.clientX, y: e.clientY });
});

window.addEventListener('mouseup', () => {
    releaseHook();
});

Using this architecture, the player can dynamically swing around geometry, reel in toward target surfaces, and launch themselves by releasing the hook mid-swing.