How to Configure Collision Filtering in Planck.js?
Planck.js, a 2D physics engine for JavaScript, allows developers to
control which fixtures collide with each other using a system of
category and mask bits. This article explains how to set up
categoryBits to define an object’s identity and
maskBits to define what it can collide with, ensuring
precise control over your game’s physics interactions. By configuring
these bitfields and assigning them to fixture definitions, you can
easily create complex collision rules, such as allowing a player to pass
through certain obstacles while still colliding with the ground.
Understanding Category and Mask Bits
Collision filtering in Planck.js relies on bitwise operations using
16-bit integers. Every fixture can be assigned a
categoryBits value and a maskBits value.
categoryBits: Represents “who I am.” It defines the collision categories this fixture belongs to.maskBits: Represents “who I collide with.” It defines the categories that this fixture is allowed to interact with.
For two fixtures to collide, the categoryBits of the
first fixture must match the maskBits of the second
fixture, and the categoryBits of the
second fixture must match the maskBits of the first
fixture.
Defining Your Collision Categories
Because Planck.js uses a 16-bit integer for filtering, you can have up to 16 unique collision categories (from \(2^0\) to \(2^{15}\)). It is best practice to define these as hexadecimal constants at the top of your script.
const CATEGORY_PLAYER = 0x0001; // 0000000000000001 in binary
const CATEGORY_ENEMY = 0x0002; // 0000000000000010 in binary
const CATEGORY_PLATFORM = 0x0004; // 0000000000000100 in binary
const CATEGORY_POWERUP = 0x0008; // 0000000000001000 in binarySetting Up the Filter in Fixture Definitions
To apply these rules, you need to pass a
filterCategoryBits and filterMaskBits property
inside your fixture definition configuration object.
Example 1: Setting up the Player
If you want the player to collide with platforms and enemies, but
pass right through powerups, you combine the target categories using the
bitwise OR (|) operator for the mask.
const playerFixtureDef = {
shape: pl.Circle(1.0),
density: 1.0,
filterCategoryBits: CATEGORY_PLAYER,
filterMaskBits: CATEGORY_PLATFORM | CATEGORY_ENEMY
};
playerBody.createFixture(playerFixtureDef);Example 2: Setting up a Powerup
If you want a powerup to only collide with the player, its mask should reflect only the player category.
const powerupFixtureDef = {
shape: pl.Box(0.5, 0.5),
isSensor: true, // Often true for items you pick up
filterCategoryBits: CATEGORY_POWERUP,
filterMaskBits: CATEGORY_PLAYER
};
powerupBody.createFixture(powerupFixtureDef);Changing Filters Dynamically
If an object’s collision rules need to change during runtime—such as
a player becoming invincible or a phased platform turning solid—you can
update the filter directly on an existing fixture using
setFilterData.
// Get the current filter data from a fixture
const filter = fixture.getFilterData();
// Update the mask bits to ignore enemies
filter.maskBits = CATEGORY_PLATFORM;
// Apply the updated filter back to the fixture
fixture.setFilterData(filter);
// Note: You may need to call world.refilter(fixture) if the
// objects are already touching and you want immediate separation.