Sticky Proximity Mines in Matter.js

This guide explains how to implement sticky proximity mines in Matter.js that securely weld to any dynamic or static physics body upon impact and detonate when a target approaches. By combining Matter.js collision events, rigid body constraints, and proximity detection through spatial queries, you can construct responsive, attachable explosives for 2D physics games and simulations.

1. Creating the Mine Body

A sticky proximity mine consists of a primary physical body that travels through the world until it makes contact with a surface. You can assign custom properties directly to the Matter.js body object to keep track of its operational state.

const { Bodies, World } = Matter;

function createStickyMine(world, x, y) {
    const mine = Bodies.circle(x, y, 12, {
        density: 0.002,
        friction: 0.8,
        render: { fillStyle: '#e74c3c' }
    });

    // Custom metadata to manage mine lifecycle
    mine.isMine = true;
    mine.isStuck = false;
    mine.isArmed = false;
    mine.proximityRadius = 120;
    mine.explosionForce = 0.08;

    World.add(world, mine);
    return mine;
}

2. Welding the Mine to the Hit Body

To weld the mine precisely at its point of impact, listen for the collisionStart event on your engine. When the mine collides with an eligible body, create a Matter.Constraint with a length of 0 and a stiffness of 1.

Because dynamic bodies rotate, you must translate the relative position vector between the mine and the target body into the target body's local coordinate space using its current rotation angle.

const { Events, Constraint, Vector } = Matter;

function enableMineWelding(engine) {
    Events.on(engine, 'collisionStart', (event) => {
        event.pairs.forEach((pair) => {
            const { bodyA, bodyB } = pair;

            const mine = bodyA.isMine ? bodyA : bodyB.isMine ? bodyB : null;
            const target = mine === bodyA ? bodyB : bodyA;

            // Prevent welding to other sensors or already stuck mines
            if (mine && !mine.isStuck && !target.isSensor) {
                mine.isStuck = true;

                // Calculate relative vector in target body's local space
                const delta = Vector.sub(mine.position, target.position);
                const localPointB = Vector.rotate(delta, -target.angle);

                const weldConstraint = Constraint.create({
                    bodyA: mine,
                    pointA: { x: 0, y: 0 },
                    bodyB: target,
                    pointB: localPointB,
                    stiffness: 1,
                    length: 0,
                    render: { visible: false }
                });

                World.add(engine.world, weldConstraint);

                // Arm the proximity sensor shortly after latching
                setTimeout(() => {
                    mine.isArmed = true;
                }, 500);
            }
        });
    });
}

3. Proximity Detection and Detonation

Once the mine is stuck and armed, monitor the distance between the mine and potential target bodies on each engine update. Using Matter.Query.circle allows you to detect any bodies that enter the mine's proximity radius without introducing extra physical sensor bodies.

const { Query, Body } = Matter;

function enableProximityDetonation(engine) {
    Events.on(engine, 'beforeUpdate', () => {
        const allBodies = Composite.allBodies(engine.world);
        const mines = allBodies.filter((b) => b.isMine && b.isArmed);

        mines.forEach((mine) => {
            // Find bodies within proximity range
            const nearbyBodies = Query.circle(allBodies, mine.position, mine.proximityRadius)
                .filter((b) => b !== mine && !b.isSensor);

            // Detonate if an unauthorized body enters the radius
            if (nearbyBodies.length > 0) {
                detonateMine(engine, mine, allBodies);
            }
        });
    });
}

function detonateMine(engine, mine, allBodies) {
    const explosionRadius = mine.proximityRadius * 1.5;

    // Apply radial outward force to all nearby bodies
    allBodies.forEach((body) => {
        if (body.isStatic || body.isSensor || body === mine) return;

        const delta = Vector.sub(body.position, mine.position);
        const distance = Vector.magnitude(delta);

        if (distance < explosionRadius && distance > 0) {
            const normal = Vector.normalise(delta);
            const dropoff = 1 - distance / explosionRadius;
            const forceMagnitude = mine.explosionForce * dropoff;
            
            Body.applyForce(body, body.position, Vector.mult(normal, forceMagnitude));
        }
    });

    // Remove associated constraints and clean up the mine
    const constraints = Composite.allConstraints(engine.world)
        .filter((c) => c.bodyA === mine || c.bodyB === mine);

    World.remove(engine.world, constraints);
    World.remove(engine.world, mine);
}

4. Initialization and Complete Integration

To put the system into operation, instantiate your Matter.js engine, bind the collision and update events, and spawn mines as needed:

const { Engine, Render, Runner, Composite } = Matter;

const engine = Engine.create();
const runner = Runner.create();
Runner.run(runner, engine);

enableMineWelding(engine);
enableProximityDetonation(engine);

// Example: Spawn a mine moving toward a target
const activeMine = createStickyMine(engine.world, 100, 300);
Body.setVelocity(activeMine, { x: 8, y: -2 });

This modular structure guarantees that mines seamlessly attach to both static walls and spinning, free-moving obstacles, arm reliably, and deliver realistic physics-based blast displacement to all surrounding entities.