This tutorial explains a bit about the physics engine included with GameMaker:Studio, specifically how collisions are dealt with. The engine itself is based on the Box2D open source physics library, and you can find further information about the base level structure of the engine from the Box2D manual, although not all the functions have been implemented in the GameMaker Language.
You can download an example file made for this tutorial by clicking this link: Collision Mask Example. The file can be loaded into GameMaker:Studio from the start-up screen by selecting Import and browsing to where you have saved the gmz.
Overview
Basic physics can be set up easily in GameMaker:Studio, and the program itself comes with a basic tutorial, an advanced joints demo and a game demo (Angry Cats), all of which are designed to help you get the most out of the engine. However one of the things that these resources don't explain is collision filtering and how best to optimise collisions when making a game.
Why is this important? Well, collisions are "expensive", in that they require extra processing by the game engine to resolve, and, due to the way Box2D is designed, you are also limited to how many different base instances can collide in a given room, which by default is 32 (see note below). For this reason it is important that you understand how to limit these base collisions in GameMaker:Studio using parents as a an unoptimised game may eventually give you the following error:
- "Unable to assign a collision category for object <OBJECT>.
- Consider using parenting to reduce collision overheads where possible."
Note: You are not limited to 32 collisions in your whole game, only on a per-room basis!
How Collisions Are Calculated
Box2D has two methods for determining whether or not collisions can occur – via grouping or through collision bits and mask bits. At the lowest engine level the decision as to whether or not two objects can collide is controlled by the following pseudo code:
// check group index
if (groupIndex is same for both fixtures)
{
if (groupIndex is positive) return true;
if (groupIndex is negative) return false;
}
// check mask and category bits
return (maskBitsA & categoryBitsB) && (categoryBitsA & maskBitsB);
What does that mean? Well, the first part of the code is what checks for collision groups. You can assign either a positive or a negative number for a fixture collision group, or you can leave it at 0. If the number is negative, then all collisions within that group (independent of their collision events) will not ever collide and if you have a collision group that is positive then all instances from that collision group will always collide, even if they have no collision event to trigger the collisions.
After the collision groups check, there is a collision bits and collision mask bits check. These checks use the bitwise AND (&) to compare the collision bit and the mask bit of the instances in collision, so if either of the checks returns true (the instances share the same collision and mask bits) the collision is resolved otherwise nothing happens.
Basically, the collision bits define what the instance can collide with and the collision mask bits define what can collide with the instance.
Examples With GameMaker:Studio
Collision bits and masks are allocated automatically by GameMaker:Studio according to whether or not a collision event is present for instances that are present within the current room, and the easiest way for you to grasp how this all works is with some simple examples...
Example 1
The simplest example of masks and bits involves one object (objectA) that can collided with itself. So it can be represented by the following bits (only the first four are shown but remember that there are actually 32):
- objectA collision bits = 0001
- objectA collision mask bits = 0001
So the bitwise AND of these two values will always result in a true return value and so the collision is resolved. See Room0 in the example file.
Example 2
For this example, lets use two objects, objectB and objectC, and have them collide with each other but not with themselves. In this case the collision mask and bits can be represented like this:
objectB collision bits = 0001
objectB mask bits = 0010
objectC collision bits = 0010
objectC mask bits = 0001
As you can see, AND objectB (or objectC) with itself and you get false, so no collision is resolved, yet AND objectB with objectC and you get true, so the collision is detected and resolved.
See Room1 in the example file.
Example 3
Lets complicate matters a bit more and in this example have the two objects (objectD and objectE) collide with each other but we will also have objectD collide with itself too. the bit/mask representation would then be as follows:
objectD collision bits = 0001
objectD mask bits = 0011
objectE collision bits = 0010
objectE mask bits = 0001
Like the previous example, objectD and objectE can collide but unlike the previous example, when objectD collides with itself the mask/bit AND results in true, so objectD will now also resolve collisions with itself.
0001 & 0011 = true
See Room2 in the example file.
Example 4
In this final simple example, our two objects will be able to collide with each other and themselves. So:
objectF collision bits = 0001
objectF mask bits = 0011
objectG collision bits = 0010
objectG mask bits = 0011
This means that the collisions will be resolved as:
0001 & 0011 = true
(objectF collides with itself)0001 & 0001 = true
(objectF collides with objectG)0010 & 0011 = true
(objectG collides with itself)
See Room3 in the example file.
Minimizing Collision Overhead
When you create a new object in GameMaker:Studio and assign that object physical properties, it will have a new collision bit assigned to it, therefore the more unique base level objects there are in your game, the fewer available number of collision bits there will be. Keep adding base objects and you will quickly run out of collision bits that can be assigned in each room.
How can you get around this? Well, the answer is to use parenting when creating your object assets in the GameMaker:Studio resource tree.
To start with, you should try and make sure hat all objects with a physical representation should collide with one another – after all, the real world is not full of ghosts! – and if you want to exclude an object from collisions it is preferable that you use collision groups where possible.
Next you should make a base "collidable" object that handles all possible collisions. This will be our parent object and most other objects should be marked as it's children so that they can inherit inherit the collisions from it. You can then exclude objects from collisions using the collision group index option.
NOTE: Physical properties are currently not inherited. Only the collision events and normal object properties, but this may change in the future.
To help you understand how this will work, consider the following example...
Example 5
You have a room with instances of four objects in it - objectH, objectI, objectJ and objectK. ObjectH, objectI and objectJ all collide with themselves and one another, but objectK will only collide with objectH. If we make a base (parent) object then assign it to objectH, objectI and objectJ these objects will inherit the collisions from it, leading to the following settings:
objectH collision bits = 0001
objectH mask bits = 0111
objectI collision bits = 0010
objectI mask bits = 0011
objectJ collision bits 0010
objectJ mask bits = 0011
objectK collision bits = 0100
objectK mask bits = 0001
What happens here is that objectH is found first by GameMaker:Studio and so is assigned to "slot 0" of the collision bits. When an instance of objectI is encountered a new slot, “slot 1”, is allocated to it since whilst objectI and objectH share a parent objectH is also capable of colliding with objectK*. However, objectJ shares exactly the same set of collisions as objectI as inherited from the parent and thus is also assigned to “slot 1”. Finally objectK is assigned to “slot 3”.
- * theoretically if we were never to encounter an instance of objectK in the room we could share “slot 0” with objectH, but for now GM pre-supposes that an instance of objectK will be created for the room at some stage.
Note that collision bit "slots" are assigned on a "first come, first served" basis in an attempt to minimise the number of bits assigned per room, which is why parenting is so important.
See Room4 in the example file.
Event Responses and Inheritance
There will come occasions when the collision of one specific object with another should lead to some kind of response, triggered in the collision event. This may lead you to believe that by assigning a collision event to a child object, you will be adding an extra collision bit to the physics engine, however, as you are essentially sharing a collision type with a parent collision no new collision bits will be assigned.
For example...
Example 6
We will have three objects for this example, objectL and objectM which inherit their collisions from a base collidable parent object that has a collision event indicating that it collides with itself.
ObjectL responds to collisions with objects of type objectL by updating the score, and so will need a collision event of it's own to detect this and run the appropriate code. Now, because objectL vs. objectL collisions are already covered by the parent object being able to collide with itself the collision settings are simply:
objectL collision bits = 0001
objectL mask bits = 0001
objectM collision bits = 0001
objectM mask bits = 0001
So you can add code into the individual child collision events without adding to the collision bits or processing overhead.
See Room5 in the example file.