Skip to main content

Collision filters and bit masks in Corona SDK

Ok, so you're working on a game and you have some objects that you need to configure how they should (or shouldn't) collide with each other. If it's the first time you're doing this you'll probably end up at the Corona Labs page about collision detection pretty soon. You read through the section "Collision Filtering" and can't help but wonder: Does it really have to be this complicated?

No, it doesn't.

There is absolutely no reason to force knowledge about implementation details like category bits and bit masks onto the developer. All you want to do is specify if objects of type A should collide with objects of type B or not. But to do this, you need to assign unique bit identifiers to each object type, calculate the bit masks yourself, make sure that two colliding object types cross reference each other and so on. It gets really nasty as the number of object types grow.

Consider the example from the Corona Labs documentation in which there are four different object categories: player, asteroid, alien and bullet. The following diagram tries to explain how the bit masks are calculated:


It's actually a quite nice diagram and it does its job of explaining well. But, had there only been a collision filter API written on a slightly higher level it wouldn't be needed at all, because the developer has no business tampering with the bit masks. In code, the filters could be defined as a Lua table like this:

local filters = {
    player = { categoryBits = 1, maskBits = 6 },
    asteroid = { categoryBits = 2, maskBits = 15 },
    alien = { categoryBits = 4, maskBits = 15 },
    bullet = { categoryBits = 8, maskBits = 6 }
}

Ok, nothing complicated about this table, but what if:
  1. You want to know which objects an asteroid can collide with?

    Answer: You need to figure out what the number 15 really means. First of all you need to know that maskBits is actually specifying the categoryBits of the object types to collide with. Second, you need to find out which categoryBits are included in this number? For a number like 15 its pretty easy (8+4+2+1), but what if that number was 175 instead?

  2. You want to change how two object categories interact with each other?

    Answer: You need to update the maskBits for both of the object categories you want to change. This means calculating new numbers for the maskBits values. Not very difficult, but still easy to get wrong.

  3. You want to add another object category that should interact with some of the existing ones?

    Answer: You'll need to work through the filter settings for all involved object categories and update the maskBits accordingly.
That's a lot of things to think about to solve a very simple problem. To handle these issues I've created a class called CollisionFilter, which gives the developer a simpler way to configure object categories and their collision filters. This is how it would look like to implement the collision matrix above using CollisionFilter:


local CollisionFilter = require("CollisionFilter")
local cf = CollisionFilter.new({ "player", "asteroid", "alien", "bullet" })
cf:setCategoryFilter("player", { "asteroid", "alien" })
cf:setCategoryFilter("asteroid", { "asteroid", "alien", "bullet" })
cf:setCategoryFilter("alien", { "alien", "bullet" })

Instead of assigning categoryBits and calculating maskBits, you just reference the object categories by name and specify if they should collide with any other object categories. No hard coded numbers.  Adding new categories or changing existing collision filters require nothing more from the developer than just defining the rules. Understanding which categories collide with each other is as easy as reading the code, no decoding of maskBits into categoryBits needed.

Note also that you don't have to specify the same collision rule twice. For example, both player and asteroid have been configured to collide with alien, so there's no need to also specify that alien should collide with player and asteroid. And for bullet, you don't need to set any filters at all since this has already been configured for the other object types. You CAN explicitly configure both ways if you think that it improves readability, but it's not necessary.

When you're ready to create your objects and need the collision filter for an object category you just request it from CollisionFilter and the categoryBits and maskBits will already be calculated for you.


local player = display.newImage( "player.png" )
physics.addBody( player, { filter = cf:getCategoryFilter("player") } )

And finally, here's the full CollisionFilter source code. Hope you like it.

Comments