Collision Detection

This guide discusses how to handle collisions between physical objects, or ignore (filter) collisions between specific object sets.

Collision Events

Physics engine collision events are exposed through the standard Corona event+listener model, with three event types:

collision

For general collision detection, you should listen for an event named "collision". The "collision" event includes phases for "began" and "ended", which signify the moments of initial contact and broken contact. If you do not implement a "collision" listener, this event will not fire.

preCollision

An event type that fires right before the objects start to interact. Depending on your game logic, you may wish to detect this event and conditionally override the collision.

For example, in a platform game, you may wish to construct "one-sided" platforms that the character can jump vertically through, but only in one direction. You might do this by comparing the character and platform position to see if the character is below the platform, and then using a short timer to make the isSensor body property temporarily true for the character object so that it passes through the platform. If you do not implement a "preCollision" listener, this event will not fire.

Note that "preCollision"" events are quite noisy, and may report many times per contact, which may affect performance. Therefore, you should only listen for these events if you need pre-collision warnings, and we also recommend that you use local listeners within the objects of interest, rather than listening globally to all "preCollision" events in the world.

postCollision

An event type that fires right after the objects have interacted. This is the only event in which the collision force is reported. See the Collision Forces section below for more on this topic. If you do not implement a "postCollision" listener, this event will not fire.

Important

Some body types will — or will not — collide with other body types. In a collision between two physical objects, at least one of the objects must be dynamic, since this is the only body type which collides with any other type. See the Physics Bodies guide for details on body types.

Propagation Rules

Collision events in Corona have a propagation model similar to touch events. You can use this to further optimize your game performance by limiting the number of events that are created. By default, a collision between two objects will fire a local event for the first object, then a local event for the second object, then a global event in the Runtime object, assuming all have active listeners enabled. However, you may only be interested in some of this information.

Any collision event handler that returns true will stop further propagation of that collision event, even if there are further listeners that would otherwise have received it. This allows you to further limit the number of events that are created and passed to the Lua side. While individual events are not very expensive, large numbers of them can affect overall performance, so limiting event propagation is a good practice.

Collision Handling

Collisions are reported between pairs of objects, and they can be detected either globally, using a Runtime listener, or locally within an object, using a table listener.

Global Collision Handling

When detected as a Runtime event, each collision event includes event.object1 and event.object2, which contain the table IDs of the two Corona display objects involved.

Because Corona display objects behave like Lua tables, you may freely add any arbitrary data to these tables, such as names, category designators, point values, or even stored functions, and then retrieve this data at collision time. For example, you may wish to store object names in an easily-accessible string format:

local crate1 = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate1, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate1.myName = "first crate"

local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate2.myName = "second crate"

local function onCollision( event )

    if ( event.phase == "began" ) then

        print( "began: " .. event.object1.myName .. " and " .. event.object2.myName )

    elseif ( event.phase == "ended" ) then

        print( "ended: " .. event.object1.myName .. " and " .. event.object2.myName )

    end
end

Runtime:addEventListener( "collision", onCollision )

Local Collision Handling

When detected with a table listener within an object, each collision event includes a self table ID, representing the object itself, and event.other which contains the table ID of the other Corona display object involved in the collision. Again, you may wish to store each object's name in string format and retrieve it during the collision event:

local crate1 = display.newImage( "crate.png" )
physics.addBody( crate1, { density=3.0, friction=0.5, bounce=0.3 } )
crate1.myName = "first crate"

local crate2 = display.newImage( "crate.png" )
physics.addBody( crate2, { density=3.0, friction=0.5, bounce=0.3 } )
crate2.myName = "second crate"

local function onLocalCollision( self, event )

    if ( event.phase == "began" ) then

        print( self.myName .. ": collision began with " .. event.other.myName )

    elseif ( event.phase == "ended" ) then

        print( self.myName .. ": collision ended with " .. event.other.myName )

    end
end

crate1.collision = onLocalCollision
crate1:addEventListener( "collision", crate1 )

crate2.collision = onLocalCollision
crate2:addEventListener( "collision", crate2 )
Important

Currently, the Box2D physics engine is liable to crash during a collision if Corona code attempts to modify objects still involved in the collision. This is because Box2D is still working out the iterated mathematics on these objects. However, your collision handler may set a flag or include a time delay via timer.performWithDelay() so that the action can occur in the next application cycle or later.

Complete removal of object(s) via display.remove() or object:removeSelf() can be executed during the same collision event time step, but the following APIs and methods can not be called during a collision event:

Multi-Element Collisions

Collision events involving a multi-element body (see the Physics Bodies guide) also return the specific body part involved in the collision. This allows for more finely-tuned game logic, for example, you may decide that colliding with the nose of a rocket does more damage than colliding with its tail fins.

Body elements are identified by a numerical index, where the first element added to the body is given the number 1, the second one is number 2, the third is number 3, and so on.

For global collision events, two additional integer values are returned:

event.element1
event.element2

These numerical properties are equal to the index of the elements in object1 and object2 that were involved in the collision. For example, object1 might be a rocket with three body elements: nose, body, and tail. If a collision occurs on its tail, the value of event.element1 would be 3. If a collision occurs on its body, the value of event.element1 would be 2. Again, this number is simply determined by the order in which you declared the elements of the body during its construction.

When collisions involve single-element bodies, both of the above values will always be 1.

Similarly, local collision events (table listeners) return two additional values:

event.selfElement
event.otherElement

As above, these values will be 1 in the case of single-element bodies, but in multi-element bodies, these values will range from 1 to the total number of elements in the body, numbered by the order in which the elements were added to the body.

Collision Forces

Once a collision has occurred, you can obtain the direct force of the collision, along with the sideways force between the two objects, which is effectively a frictional force. For example, you may have game objects that should be destroyed only if a collision is sufficiently forceful.

The direct force of the collision is reported within the "postCollision" event as event.force, and the frictional force is available as event.friction.

local function onPostCollision( event )

    if ( event.force > 1.0 ) then
        print( "Collision force: " .. event.force .. " Friction: " .. event.friction )
    end
end

Runtime:addEventListener( "postCollision", onPostCollision )

In the example above, very small forces are screened out. Also, a global listener is used here to simplify the example.

Since the "postCollision" event stream from the physics engine can be quite noisy (it registers a series of increasingly tiny forces as objects "settle"), you will probably want to choose your own threshold for ignoring some of it.

Technical Note

Box2D is actually reporting collision impulses, which are forces multiplied by the elapsed time of each world step, but since this time interval is always the same, this distinction can be ignored for the purposes of game logic.

Collision Filtering

By default, all bodies collide with all other bodies, but you may want to fine-tune this behavior. For example, in the classic arcade game Asteroids, none of the asteroids collide with other asteroids, but all of them collide with the player and with the enemy spaceships. There are two ways to accomplish this.

categoryBits/maskBits

First, you can assign categoryBits and maskBits to your objects via a "collision filter" definition, which is an optional table assigned during body construction. An object will only collide with other objects if their categoryBits are among its assigned maskBits. Normally, an object will only have one category bit assigned, but may have one or more mask bits depending on what other things it should collide with.

In the example below, the red square has a maskBits value of 3, so it would collide with any objects in category bits 2 and 1, which in this case would include other red squares (category bit 2) or the wall objects (category bit 1). Meanwhile, it will pass through any blue squares (category bit 4), since bit 4 is not included in its maskBits value.

local floorCollisionFilter = { categoryBits=1, maskBits=6 } 
-- floor collides with (4 and 2) only

local floor = display.newRect( 0, 0, 320, 80 )
physics.addBody( floor, "static", { bounce=0.8, filter=floorCollisionFilter } )

local redCollisionFilter = { categoryBits=2, maskBits=3 } 
-- red collides with (2 and 1) only

local blueCollisionFilter = { categoryBits=4, maskBits=5 } 
-- blue collides with (4 and 1) only

local redSquare = display.newRect( 0, 80, 40, 40 )
redSquare:setFillColor( 1, 0, 0 )
physics.addBody( redSquare, { friction=0, filter=redCollisionFilter } )

local blueSquare = display.newRect( 80, 80, 40, 40 )
blueSquare:setFillColor( 0, 0, 1 )
physics.addBody( blueSquare, { friction=0, filter=blueCollisionFilter } )

Collision Filter Worksheet

Collision filter values can be calculated using a simple cross-reference worksheet. Consider the following example based on the classic arcade game "Asteroids" which had four basic object types which could potentially collide: the player, asteroids, aliens, and bullets fired from the player.

  1. Observe the numbers in the uppermost row: 1 to 512 in binary progression. These "bit" values are the essential aspect in determining collision filter values. For complex games, you can continue past 512 — up to 32768 (16 columns) if necessary — but 10 columns is enough for most games.

  2. Now observe the rows for each object type. Since each object type must have two values for its collision filter — categoryBits and maskBits — each row is sub-divided into category and collides with.

  3. For each object type in the game, pick one bit number from the uppermost row and mark a circled in its associated category sub-row. This number should be unique for each object type — do not use the same category number for multiple object types.

  4. Next, decide if each object type can collide with other objects of the same type. If true, mark an × directly below the in the collides with sub-row. In this example, notice that asteroids should collide with other asteroids and aliens should collide with other aliens, but bullets should not collide with other bullets.

  5. Next, for each object type, inspect all other object types both above and below its row. If the object type should collide with any other object type, mark an × in the collides with sub-row of the other type. Remember to keep each × in the same column as the category bit value () that you chose for the object type.

  6. Note that collision filters work in "reflex" and you must consider all colliding object types in association. For example, asteroids and aliens should collide with bullets, so it may be tempting to mark an × for each of these in the bullet collides with row and then stop the process. However, you must also mark an × in the bullet's bit number column for both asteroids and aliens. If you fail to consider object types in this association, collision filters will not function properly.

  7. When all object types are complete, work through the chart row by row to determine the sum column values. For each sub-row, add (sum) the total bit value of all marks. For example, the player collides with sub-row has an × in bit columns 2 and 4, so the correct sum value is 6.

  8. When all sum values are calculated, simply set the categoryBits value for the collision filter to the category sum. Similarly, set the maskBits value of the filter to the collides with sum. For example, the player object with sums of 1 and 6 may be configured as follows:

local playerCollisionFilter = { categoryBits=1, maskBits=6 }

local player = display.newImage( "player.png" )
physics.addBody( player, { filter=playerCollisionFilter } )
  1. Remember that if you add additional game elements later, you must work through the entire chart (all object types) again, because if the new object type should collide with any previous object types, values may change for those previous object types. As such, it's recommended that you declare your filters as Lua variables which can be accessed by multiple game objects, regardless of when and where you create them.

groupIndex

An alternative collision filtering method is to assign a groupIndex to each object. This value can be a positive or negative integer and it may be a simpler way of specifying collision rules. Objects with the same positive groupIndex value will always collide with each other, and objects with the same negative groupIndex value will never collide with each other.

local greenCollisionFilter = { groupIndex = -2 }

If both groupIndex and collisionBits/maskBits are assigned to an object, the groupIndex has higher precedence.