Simulate Pinball Roll-Over Switches in Matter.js
This article explains how to implement pinball lane roll-over switches using Matter.js sensor bodies. By utilizing static sensor fixtures combined with collision event listeners, you can detect when a pinball passes through a specific lane to trigger scores, lights, or game mechanics without affecting the ball's natural trajectory or velocity.
Understanding Sensor Bodies
In traditional 2D physics simulations, dynamic bodies collide and rebound off one another. However, a pinball roll-over switch—whether a wire rollover or a star rollover—should detect the ball passing over it without physically blocking it.
Matter.js provides the isSensor property on rigid
bodies. When isSensor: true is set, the body stops
generating physical contact responses (meaning bodies pass completely
through it), but it continues to register collision detection pairs and
dispatch collision events.
Step 1: Defining the Sensor Body
To simulate the switch, create a static rectangular or circular body placed within your lane boundaries and flag it as a sensor. Label the body so it can be easily identified in collision handlers.
const { Bodies, World } = Matter;
// Define the rollover switch
const rollOverSwitch = Bodies.rectangle(200, 350, 40, 10, {
isStatic: true,
isSensor: true,
label: 'rollover_lane_1',
render: {
fillStyle: '#ffcc00',
opacity: 0.5
}
});
// Define the pinball
const pinball = Bodies.circle(200, 100, 12, {
label: 'pinball',
restitution: 0.5,
density: 0.004
});
World.add(engine.world, [rollOverSwitch, pinball]);Step 2: Listening for Collision Events
Matter.js dispatches collision events via Matter.Events.
To trigger game logic when the ball rolls over the switch, listen to the
collisionStart event. Check the collision pairs to confirm
whether one body is the switch and the other is the pinball.
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];
const isRollOverTriggered =
(bodyA.label === 'rollover_lane_1' && bodyB.label === 'pinball') ||
(bodyB.label === 'rollover_lane_1' && bodyA.label === 'pinball');
if (isRollOverTriggered) {
activateRollOverSwitch('lane_1');
}
}
});Step 3: Managing State and Debouncing
Because a ball may trigger multiple collision checks while passing over a wide switch area, you should manage activation state to prevent unintended score multiplications.
const laneStates = {
lane_1: { lit: false, active: false }
};
function activateRollOverSwitch(laneId) {
const lane = laneStates[laneId];
// Prevent repeated triggers during a single pass
if (lane.active) return;
lane.active = true;
// Toggle lane illumination and award points
lane.lit = !lane.lit;
addScore(500);
// Play audio or visual feedback here
console.log(`${laneId} triggered. Lit: ${lane.lit}`);
}
// Reset the switch trigger once the ball completely leaves the sensor area
Events.on(engine, 'collisionEnd', (event) => {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const { bodyA, bodyB } = pairs[i];
if (
(bodyA.label === 'rollover_lane_1' && bodyB.label === 'pinball') ||
(bodyB.label === 'rollover_lane_1' && bodyA.label === 'pinball')
) {
laneStates.lane_1.active = false;
}
}
});Using static sensor bodies alongside paired
collisionStart and collisionEnd events gives
you complete control over lane logic while preserving the realistic
momentum of the pinball.