Collision Detection

Collision events

Physics engine collision events are exposed through the standard Corona event-listener model, with three new event types. 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. These phases exist for both normal two-body collisions and body-sensor collisions. If you do not implement a "collision" listener, this event will not fire.

In addition to "collision" events, two other event types are optionally available whenever two bodies (not sensors) collide. Previously, these were also phases of the "collision" event type, but they have now been separated into unique event types, since they have more specialized uses.

"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 "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.

"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.

Note: The “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.

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. See the "CollisionDetection" sample code for working examples of global and local collision listeners.

 

Important Restriction: Collision handling

Currently, the Box2D physics engine will reliably crash during a collision if Corona code attempts to modify objects still involved in the collision (since Box2D is still working out iterated mathematics on them).

The standard workaround is to "wait a bit":

  1. Do not modify / create / destroy physics objects during a collision, on penalty of crashing.
  2. If you need to modify / create / destroy an object as a result of a collision, your collision handler should set a flag or add a time delay so the change can occur later, e.g. with timer.performWithDelay().

 

Global collision listeners


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. This is similar to the userData attribute in Box2D, but it is essentially unlimited, since you can assign as many custom attributes as you like.

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 .. " & " .. event.object2.myName )
 
        elseif ( event.phase == "ended" ) then
 
                print( "ended: " .. event.object1.myName .. " & " .. event.object2.myName )
 
        end
end
 
Runtime:addEventListener( "collision", onCollision )

 

Local collision listeners


When detected with a table listener within an object, each collision event includes 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 )

See the "CollisionDetection" sample code for examples of both of these methods. (Run the project using Corona Terminal to see the output of the collisions.)

 

Collisions with multi-element bodies


Collision events involving a multi-element body (see "Complex Body Construction") 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 tailfins.

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 now 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 a rocket with three body elements: nose, body and tail. If the collision is with its tail, the value of event.element1 would be 3; if the collision is with 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) now have additional fields:

event.selfElement
event.otherElement

As above, these values will be 1 in the case of single-element bodies, and in multi-element bodies 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. See the "CollisionDetection" sample project for an example of how to do this (run the project using Corona Terminal to see the output of the collisions.)

(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 categories, masking, and groups


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 should collide with the player and with the enemy spaceships. There are two ways to accomplish this.

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.

(Update: see the helpful collision filter worksheet and discussion posted here.)

In the example below, the red balloon 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 balloons (category bit 2) or the wall objects (category bit 1). Meanwhile, it will pass through any blue balloons (category bit 4), since bit 4 is not included in its maskBits value.

borderCollisionFilter = { categoryBits = 1, maskBits = 6 } 
-- border collides with (4 & 2) only
 
borderBody = { friction=0.4, bounce=0.8, bodyType="static", filter=borderCollisionFilter }
 
local borderTop = display.newRect( 0, 0, 320, 1 )
physics.addBody( borderTop, borderBody )
 
local borderBottom = display.newRect( 0, 479, 320, 1 )
physics.addBody( borderBottom, borderBody )
 
local borderLeft = display.newRect( 0, 1, 1, 480 )
physics.addBody( borderLeft, borderBody )
 
local borderRight = display.newRect( 319, 1, 1, 480 )
physics.addBody( borderRight, borderBody )
 
local redCollisionFilter = { categoryBits = 2, maskBits = 3 } 
-- red collides with (2 & 1) only
 
local blueCollisionFilter = { categoryBits = 4, maskBits = 5 } 
-- blue collides with (4 & 1) only
 
local redBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=redCollisionFilter }
local blueBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=blueCollisionFilter }
 
redBalloon = display.newImage( "red_balloon.png" )
physics.addBody( redBalloon, redBody )
 
blueBalloon = display.newImage( "blue_balloon.png" )
physics.addBody( blueBalloon, blueBody )

Note that categoryBits and maskBits are binary values that are currently assigned using decimal numbers. This is convenient for lower bits like 1, 2, 4 and 8, but not so convenient for higher bits -- since there are 16 possible categoryBits, the highest bit value is 32768. Therefore, future versions of this API may use a different bit assignment syntax, such as binary or hexadecimal values.

The second method is to assign a groupIndex to each object, via the same filter definition shown above. This value can be a positive or negative integer, and is 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.

See the "CollisionFilter" sample code for an example of collision filtering.

 

Collision event propagation rules

Collision events in Corona now 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. For example, in a game with one player character and lots of enemies, you may only be interested in what the player has collided with.

As of Alpha 3, 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.

Replies

Viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Tom
User offline. Last seen 12 hours 44 min ago. Offline
Staff
Joined: 13 Jul 2010

Please post links and comments in the forums and not on the doc pages.
These comments will be deleted.

Thanks.