Practical Guide to Matter Physics Collision Filtering in Phaser 3

Learn how groups, categories, and masks work so you can use them confidently

by on 6 minute read updated on


Are you using collision filters in Matter Physics to better control what should or shouldn't collide with each other?

Matter collision filtering can be confusing even though Phaser makes it easier than using the matterjs library directly.

If you are having a hard time understanding groups, categories, and masks or got the collisions to work by trial and error but don't understand why it works then this article is for you!

We will show you how things work with clear examples so that you can apply it confidently!

Collision Groups

First, let's talk about groups. The default value for collisionFilter.group is 0. This means that the default behavior is for the collision system to use the category/mask rules.

The practical outcome is that all bodies collide with each other.

Now, let's say we create two MatterSprites and set the group to different values:

1
2
3
4
5
const ship1 = this.matter.add.sprite(100, 100, 'ship')
ship1.setCollisionGroup(1)

const ship2 = this.matter.add.sprite(200, 200, 'ship')
ship2.setCollisionGroup(2)

Each ship has a different group value so they should not collide, right?

Nope. 🧐

This may feel unexpected as the docs say:

If the two bodies have the same non-zero value of collisionFilter.group, they will always collide if the value is positive, and they will never collide if the value is negative.

Clearly, we have 2 non-zero positive values that are different: 1 != 2.

So why are they colliding?

This is where the second half of the rules come in. If the group values are positive or zero and different from each other then it will use the category/mask rules.

The outcome is the same as having the default group value of 0.

We can have the group values work by setting the collisionFilter.mask property to 0.

1
2
3
4
5
6
7
const ship1 = this.matter.add.sprite(100, 100, 'ship')
ship1.setCollisionGroup(1)
ship1.setCollidesWith(0)

const ship2 = this.matter.add.sprite(200, 200, 'ship')
ship2.setCollisionGroup(2)
ship2.setCollidesWith(0)

Now, the two ships will no longer collide with each other. Phaser 3 uses setCollidesWith() as a more understandable way to set the collisionFilter.mask property.

You can also directly set the collisionFilter property of the Matter body like this:

1
2
3
4
const ship1 = this.matter.add.sprite(100, 100, 'ship')

ship1.body.collisionFilter.group = 1
ship1.body.collisionFilter.mask = 0

The key point to remember is that if you want to use groups and ignore the category/mask rules then you need to set the mask property to 0. The mask property is set to -1 by default which means everything will collide with each other.

Collision Category/Mask

The category/mask system is more complicated but also more powerful. It uses a bitmask system and this is where most people get lost.

Phaser will shield you from having to deal with bitmasks as long as you use the setCollidesWith() method. But if you are trying to read the documentation then very little will make sense without some understanding of bitmasks.

Bitmasks for Simplicity

You may have heard that all computer programs eventually get turned into a series of 1's and 0's. This is the native language of all computers called binary or machine language. We read and write in code but the computer only understands 1 (on) or 0 (off).

All the code we write can be represented in binary including numbers. Binary representaton of numbers looks like this:

0 = 0000
1 = 0001
2 = 0010
4 = 0100
8 = 1000

You'll notice that we are only showing numbers that are a power of 2 from zero to eight. Numbers like 3, 5, 6, or 7 can also be represented but they don't matter for how we'll use bitmasks.

You can have up to 32 collision categories in Matter because of this system where each 1 needs to be by itself in a row of 0's. This is also known as a 32-bit integer because of the 32 binary spots used to represent numbers. Each spot is technically called a bit. 😎

A bitmask is the combination of these power of 2 numbers. We can use this to check which flags are set depending on which bit is set to 1.

Let's say you have these 3 collision categories:

1
2
3
const category1 = 0b0001
const category2 = 0b0010
const category3 = 0b0100

Notice that they are the binary representations of 1, 2, and 4. We can make a mask of category1 and category3 like this:

1
2
3
4
5
const masks = category1 | category3
// 0b0001
// 0b0100
// =
// 0b0101

The pipe symbol (|) is the OR bitwise operator meaning that it will combine the two values and set the resulting bit to 1 if there is a 1 at that bit position in either value.

Using this mask we can check which of the 3 categories are included in a very efficient manner.

That's the basics of bitmasks that you'll need to know to understand what the Matter Physics docs are saying.

Using Categories and Masks

There are 2 key things to understand when using Matter's category/mask rule and they are:

  • each Matter body should have a category
  • each Matter body mask should include the categories it will collide with

Let's use our ship example again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const CATEGORY_PLAYER = 0b0001
const CATEGORY_ENEMY = 0b0010

const player = this.matter.add.sprite(100, 100, 'ship')
player.setCollisionCategory(CATEGORY_PLAYER)
player.setCollidesWith(CATEGORY_ENEMY)

const enemy = this.matter.add.sprite(200, 200, 'ship')
enemy.setCollisionCategory(CATEGORY_ENEMY)
enemy.setCollidesWith(CATEGORY_PLAYER)

We have a player and an enemy and they should collide with each other. Notice that we set the player category to CATEGORY_PLAYER and the mask to CATEGORY_ENEMY with setCollidesWith().

Then we do the inverse for the enemy where the category is CATEGORY_ENEMY and the mask is CATEGORY_PLAYER.

This satisfies the 2 rules where each body has a category and their mask has the category that they should collide with.

Next, let's say the player shoots lasers and enemies should collide with those:

1
2
3
4
5
6
7
const CATEGORY_PLAYER_LASERS = 0b0100

// other code...

const enemy = this.matter.add.sprite(200, 200, 'ship')
enemy.setCollisionCategory(CATEGORY_ENEMY)
enemy.setCollidesWith([CATEGORY_PLAYER, CATEGORY_PLAYER_LASERS])

You can pass an array of categories to setCollidesWith() and Phaser will automatically create the appropriate bitmask.

You can also apply your newly learned bitmask knowledge by doing this instead:

1
enemy.setCollidesWith(CATEGORY_PLAYER | CATEGORY_PLAYER_LASERS)

Next Steps

You should now be able to use collision groups, categories, and masks confidently!

Remember that these seemingly complicated computer concepts were designed by other humans and being able to understand them is within your abilities. 🤗 Might just take some work.

Be sure to sign up for our newsletter so you don't miss any future Phaser 3 game development tips and techniques!

Drop your email into the box below.

Don't miss any Phaser 3 game development content

Subscribers get exclusive tips & techniques not found on the blog!

Join our newsletter. It's free. We don't spam. Spamming is for jerks.

Phaser 3 physics matter matterjs matter physics collision filter

Want tips and techniques more suited for you?


You may also like...


Video Guides


Beginner Guides


Articles Recommended For You

Fix Stretched Image Distortions in Phaser 3 with 9-Slice Scaling

by on

Are you having image distortion problems when scaling to make a button or panel graphic bigger? Multiple versions of the …

5 minute read

Command Pattern to Undo Player Actions

by on

Are you looking for a clean and reusable way to implement undo for player actions? Perhaps you are making a turn-based …

15 minute read

Advanced Logging with the Strategy Pattern

by on

Have you ever tried debugging a problem with your game that only seems to happen in production? The Developer Tools …

7 minute read

State Pattern for Character Movement in Phaser 3

by on

Writing clean and well-organized code is something all game developers aspire to. We want code to be reusable and easy …

7 minute read

Didn't find what you were looking for?


comments powered by Disqus