How to Use BeginContact in Planck.js?
This article provides a practical guide on how to utilize the
BeginContact event in planck.js to detect the exact moment
two fixtures start colliding. You will learn how to set up a contact
listener, access the fixtures involved in the collision, and implement
custom logic like scoring or triggering visual effects when objects
touch.
Setting Up the Contact Listener
In planck.js (a 2D physics engine based on Box2D), collision handling
is managed by registering listener functions on the physics world
object. The BeginContact event fires during the physics
step immediately after two fixtures overlap for the first time.
To start listening for these collisions, you need to call
world.on('begin-contact', callback).
// Initialize your planck.js world
const world = planck.World();
// Register the BeginContact listener
world.on('begin-contact', function(contact) {
// Collision handling logic goes here
});Extracting Fixtures and Bodies
The contact object passed into your callback function
contains all the data regarding the collision. To find out which
specific objects collided, you must extract the fixtures, and
optionally, the rigid bodies they are attached to.
contact.getFixtureA(): Returns the first fixture in the collision.contact.getFixtureB(): Returns the second fixture in the collision.
world.on('begin-contact', function(contact) {
const fixtureA = contact.getFixtureA();
const fixtureB = contact.getFixtureB();
const bodyA = fixtureA.getBody();
const bodyB = fixtureB.getBody();
});Identifying Specific Game Objects
Because BeginContact triggers for every
collision in the world, you need a way to filter and identify the
interacting objects. The most common method is using the
setUserData() and getUserData() methods on
fixtures or bodies to store identifiers.
1. Assigning Labels during Creation
// Player Body
const playerBody = world.createBody();
const playerFixture = playerBody.createFixture(planck.Circle(1.0));
playerFixture.setUserData({ type: 'player' });
// Coin Body
const coinBody = world.createBody();
const coinFixture = coinBody.createFixture(planck.Box(0.5, 0.5));
coinFixture.setUserData({ type: 'coin' });2. Filtering inside the Listener
world.on('begin-contact', function(contact) {
const dataA = contact.getFixtureA().getUserData();
const dataB = contact.getFixtureB().getUserData();
// Check if one is a player and the other is a coin
if ((dataA?.type === 'player' && dataB?.type === 'coin') ||
(dataB?.type === 'player' && dataA?.type === 'coin')) {
console.log('Player collected a coin!');
// Trigger gameplay logic here (e.g., increase score)
}
});Important Considerations
- Physics Step Lock: You cannot safely destroy bodies
or modify the world topology (like creating new joints or bodies)
directly inside the
BeginContactcallback. If you need to destroy a collided object (e.g., deleting a collected coin), store a reference to the body in an array and destroy it after the current physics step completes. - Sensors: If you want to detect overlaps without
physical bouncing or deflection, make sure to set
fixture.setSensor(true)on your triggers.BeginContactwill still fire normally for sensors.