Trigger Sound on Collision in Matter.js

Triggering a sound effect when two specific bodies collide in Matter.js requires identifying the interacting physics bodies and listening for the engine's collision events. By assigning unique labels to your bodies, monitoring the collisionStart event on the engine, and playing an audio element when those labels match, you can seamlessly integrate collision-based audio into your web application or game.

1. Assign Identifiers to Your Bodies

When creating bodies using Matter.Bodies, assign a custom label property to each body. This makes it straightforward to detect which specific objects are interacting during a collision.

const ball = Matter.Bodies.circle(100, 100, 20, {
  label: 'ball'
});

const ground = Matter.Bodies.rectangle(400, 600, 810, 60, {
  isStatic: true,
  label: 'ground'
});

Matter.Composite.add(engine.world, [ball, ground]);

2. Prepare the Audio Element

Load your audio using the standard HTML5 Audio constructor or an <audio> tag in your HTML.

const bounceSound = new Audio('bounce.mp3');

To ensure the sound plays even if the previous playback hasn't finished, clone the node or reset currentTime to zero before calling play().

function playBounceSound() {
  bounceSound.currentTime = 0;
  bounceSound.play().catch(error => {
    // Handle browser autoplay policies if triggered before user interaction
    console.warn('Audio playback prevented:', error);
  });
}

3. Listen for the Collision Event

Matter.js provides collision lifecycle events on the engine object. Use Matter.Events.on(engine, 'collisionStart', callback) to intercept the moment two bodies make contact.

Iterate through the event.pairs array and check if the pair contains the two target labels.

Matter.Events.on(engine, 'collisionStart', (event) => {
  const pairs = event.pairs;

  for (let i = 0; i < pairs.length; i++) {
    const bodyA = pairs[i].bodyA;
    const bodyB = pairs[i].bodyB;

    // Check if the collision is between 'ball' and 'ground'
    const isBallAndGround = 
      (bodyA.label === 'ball' && bodyB.label === 'ground') ||
      (bodyA.label === 'ground' && bodyB.label === 'ball');

    if (isBallAndGround) {
      playBounceSound();
      break;
    }
  }
});

Alternative: Checking by Object Reference

If your bodies are dynamic or you do not want to rely on string labels, you can check directly against the body references:

const isTargetCollision = 
  (bodyA === ball && bodyB === ground) ||
  (bodyA === ground && bodyB === ball);

if (isTargetCollision) {
  playBounceSound();
}

Velocity-Based Playback (Optional)

To make sounds feel realistic, check the collision's relative velocity. You can prevent sound playback on very light touches or scale the audio volume based on impact speed by accessing pairs[i].collision.depth or the bodies' velocities:

const speedA = Math.hypot(bodyA.velocity.x, bodyA.velocity.y);
const speedB = Math.hypot(bodyB.velocity.x, bodyB.velocity.y);
const relativeSpeed = Math.abs(speedA - speedB);

if (relativeSpeed > 1.5) {
  // Scale volume between 0.1 and 1.0 based on impact
  bounceSound.volume = Math.min(Math.max(relativeSpeed / 10, 0.1), 1.0);
  playBounceSound();
}