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.