Build Goal Pocket Trigger Zones in Matter.js
This guide explains how to implement goal pocket trigger zones in Matter.js to accurately detect and register scoring events. By configuring rigid bodies as non-colliding sensors and attaching custom event listeners to the physics engine, you can detect when a ball or puck enters a designated pocket without disrupting the natural physical movement of other objects on the board.
1. Define the Goal Pocket as a Sensor Body
In Matter.js, standard bodies produce solid physical collisions that
bounce objects away. To create an invisible trigger zone, set the
isSensor property to true. A sensor body
detects intersections with other objects without imparting any physical
forces back onto them.
const { Bodies, Composite } = Matter;
// Create the goal trigger zone
const goalPocket = Bodies.circle(400, 550, 30, {
isSensor: true,
isStatic: true,
label: 'goalPocket',
render: {
fillStyle: 'transparent',
strokeStyle: '#00ff00',
lineWidth: 2
}
});
// Create the scoring object (e.g., a ball)
const ball = Bodies.circle(400, 100, 15, {
restitution: 0.8,
label: 'ball'
});
Composite.add(engine.world, [goalPocket, ball]);2. Listen for Collision Events
Matter.js dispatches collision lifecycle events through
Matter.Events. To register scoring the instant an object
crosses into the trigger area, bind a handler to the
collisionStart event.
Iterate through event.pairs to inspect colliding
bodies:
const { Events } = Matter;
Events.on(engine, 'collisionStart', (event) => {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const { bodyA, bodyB } = pairs[i];
// Check if one body is the goal pocket and the other is the ball
const isGoalA = bodyA.label === 'goalPocket';
const isGoalB = bodyB.label === 'goalPocket';
const isBallA = bodyA.label === 'ball';
const isBallB = bodyB.label === 'ball';
if ((isGoalA && isBallB) || (isGoalB && isBallA)) {
const scoringBall = isBallA ? bodyA : bodyB;
handleScore(scoringBall);
}
}
});3. Handle Scoring Logic and Prevent Double Counting
When a fast-moving object intersects multiple sub-steps or vibrates within a pocket, it can trigger multiple collisions if not properly managed. To handle this, implement a state flag, debounce check, or immediately remove/reset the ball once scored:
let score = 0;
function handleScore(ballBody) {
score += 1;
console.log(`Goal scored! Current score: ${score}`);
// Prevent repeated collisions by resetting or removing the ball
Matter.Body.setVelocity(ballBody, { x: 0, y: 0 });
Matter.Body.setPosition(ballBody, { x: 400, y: 100 });
}4. Optimize Detection with Collision Filtering
If your game world contains multiple non-scoring objects (such as obstacles, walls, or players), use collision categories and masks so the trigger zone only checks for the ball:
const CATEGORY_DEFAULT = 0x0001;
const CATEGORY_BALL = 0x0002;
const CATEGORY_GOAL = 0x0004;
// Ball setup
ball.collisionFilter = {
category: CATEGORY_BALL,
mask: CATEGORY_DEFAULT | CATEGORY_GOAL
};
// Goal pocket setup (only detects the ball)
goalPocket.collisionFilter = {
category: CATEGORY_GOAL,
mask: CATEGORY_BALL
};This ensures the physics engine avoids checking calculations between the trigger zone and irrelevant static elements, keeping scoring detection fast and accurate.