Matter.js: Change Body Collision Filter at Runtime

Modifying body collision filters dynamically at runtime in Matter.js is essential for mechanics such as passing through one-way platforms, toggling invulnerability frames, or switching entity teams. This article explains how Matter.js calculates collision rules, how to directly mutate the collisionFilter properties on active physics bodies during the simulation cycle, and how to correctly apply bitwise masks for complex interactions.

Understanding collisionFilter

Every Matter.js body has a collisionFilter object containing three primary properties:

Two bodies (Body A and Body B) will collide if and only if:

  1. Their group values are non-zero and identical (if positive, they collide; if negative, they do not).
  2. If group is zero (or unequal), they collide when (BodyA.category & BodyB.mask) !== 0 and (BodyB.category & BodyA.mask) !== 0.

Modifying the Filter at Runtime

To change collision behavior during the game loop or in response to an event, directly update the properties of body.collisionFilter. There is no need to remove and re-add the body to the engine; Matter.js evaluates collision pairs on every tick of the engine.

// Define custom categories as bitflags
const CATEGORY_DEFAULT = 0x0001;
const CATEGORY_PLAYER  = 0x0002;
const CATEGORY_ENEMY   = 0x0004;
const CATEGORY_PASSABLE = 0x0008;

// Example: Turn off collisions between a player and enemies (e.g., during a dash or invulnerability)
function enablePlayerGhostMode(playerBody) {
    // Modify the mask to collide with DEFAULT, but exclude ENEMY
    playerBody.collisionFilter.mask = CATEGORY_DEFAULT;
}

function disablePlayerGhostMode(playerBody) {
    // Restore mask to collide with both DEFAULT and ENEMY
    playerBody.collisionFilter.mask = CATEGORY_DEFAULT | CATEGORY_ENEMY;
}

Using Bitwise Operators for Dynamic Changes

Bitwise operators make it straightforward to add or remove specific interaction layers on the fly without overwriting existing settings:

Instant Separation with the group Property

If you need two bodies to immediately ignore each other without recomputing bitmasks, assign them matching negative group IDs at runtime:

// Prevent dynamicBodyA and dynamicBodyB from colliding
const uniqueGroupId = Matter.Body.nextGroup(true); // Generates a negative ID
dynamicBodyA.collisionFilter.group = uniqueGroupId;
dynamicBodyB.collisionFilter.group = uniqueGroupId;

To re-enable collision rules based on category and mask, reset collisionFilter.group to 0 on both bodies.