Building a Scoring System with Matter.js Collisions
This guide explains how to implement a functional scoring system using Matter.js collision events. You will learn how to assign custom identifiers to physics bodies, listen for collision events using the Matter.js event system, filter specific interactions between objects, and reliably update the score while preventing duplicate points.
1. Labeling Matter.js Bodies
To track which objects trigger score changes, identify them by
setting custom properties on their body definitions. The standard way is
using the label property or custom attributes like
scoreValue.
const player = Matter.Bodies.circle(100, 100, 20, {
label: 'player'
});
const coin = Matter.Bodies.circle(300, 300, 15, {
label: 'coin',
isStatic: true,
scoreValue: 10
});
const hazard = Matter.Bodies.rectangle(200, 400, 50, 50, {
label: 'hazard',
isStatic: true,
scoreValue: -5
});
Matter.Composite.add(engine.world, [player, coin, hazard]);2. Listening for
collisionStart Events
Matter.js provides collision event hooks via the
Matter.Events module. The collisionStart event
fires during the tick in which two bodies first make contact.
let currentScore = 0;
Matter.Events.on(engine, 'collisionStart', (event) => {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const { bodyA, bodyB } = pairs[i];
handleCollision(bodyA, bodyB);
}
});3. Handling Pairs and Updating the Score
Because collisions involve two bodies where the order of
bodyA and bodyB is arbitrary, verify both
combinations to see if a scoring interaction occurred.
function handleCollision(bodyA, bodyB) {
// Identify if the player collided with a scoring object
let target = null;
if (bodyA.label === 'player' && targetLabels.includes(bodyB.label)) {
target = bodyB;
} else if (bodyB.label === 'player' && targetLabels.includes(bodyA.label)) {
target = bodyA;
}
if (target) {
processScore(target);
}
}
const targetLabels = ['coin', 'hazard'];
function processScore(targetBody) {
if (targetBody.scoreValue) {
currentScore += targetBody.scoreValue;
updateScoreDisplay(currentScore);
// Remove the item from the world if it's a consumable like a coin
if (targetBody.label === 'coin') {
Matter.Composite.remove(engine.world, targetBody);
}
}
}
function updateScoreDisplay(score) {
document.getElementById('score-board').innerText = `Score: ${score}`;
}4. Preventing Duplicate Score Triggers
If an object is not removed immediately, multiple collision events
might trigger across consecutive frames or between sub-steps. To ensure
a single body only rewards points once, use a boolean flag or a
Set to track processed items.
const collectedBodies = new Set();
function processScoreSafe(targetBody) {
if (collectedBodies.has(targetBody.id)) {
return;
}
collectedBodies.add(targetBody.id);
currentScore += targetBody.scoreValue || 0;
updateScoreDisplay(currentScore);
// Schedule removal from physics world
Matter.Composite.remove(engine.world, targetBody);
}Using unique body identifiers (targetBody.id) ensures
that your scoring logic remains accurate, decoupled from rendering
rates, and free of multi-trigger bugs.