Use Box2d for Collision Detection with Corona SDK – Tutorial Part 3

by Jo Meenen | Click to Follow Him on Twitter

Welcome back to the Corona SDK tutorial series. You might want to consider reading part one and part two first.

In this third part I will show you how you can utilize the physics simulation lib Box2d for collision detection. Box2d is a physics simulation package with many interesting features. We will use only one of those for our game: Collision Detection

To ensure that we have the same basis for this tutorial, please download the code we have written so far. Do not forget to rename the file to main.lua.

Coerce the Player to Stay on Screen

You might have noticed that the player can move off screen. To ensure that the player will stay within the screen bounds, add the following function just above the onTouch function:

-- Forces the object to stay within the visible screen bounds.
local function coerceOnScreen( object )
    if object.x < object.width then
        object.x = object.width
    end
    if object.x > display.viewableContentWidth - object.width then
        object.x = display.viewableContentWidth - object.width
    end
    if object.y < object.height then
        object.y = object.height
    end
    if object.y > display.viewableContentHeight - object.height then
        object.y = display.viewableContentHeight - object.height
    end
end

And call this function from within the touch handler onTouch:

...
player.y = event.y - player.y0
coerceOnScreen( player )
elseif "ended" == phase or "cancelled" == phase then
...

This code resets the player’s position, if it has been moved out of the screen bounds.

Run the code in your simulator. You shouldn’t be able to move the player off screen anymore.

Physics Simulation

Before our app can receive collision detection notifications from Corona, we need to add the Box2d library to the project. This can be achieved easily by the following Lua code, which you should paste just above the createPlayer function:

--Set up the physics world
local physics = require( "physics" )
physics.start()
--physics.setDrawMode( "hybrid" )

With the require( "physics" ) directive we let Corona know that we want to use the Box2d library. We start the physics simulation by calling physics.start(). The setDrawMode() function is a helper function we can use to debug the code related to the Box2d library.

Now we need to add the player to the physics simulation. Replace the createPlayer function with this code:

local function createPlayer(x, y, width, height, rotation)
    --  Player is a black square
    local p = display.newRect(x, y, width, height)
    p:setFillColor(0, 0, 0)
    p.rotation = rotation

    local playerCollisionFilter = { categoryBits = 2, maskBits = 5 }
    local playerBodyElement = { filter=playerCollisionFilter }

    p.isBullet = true
    p.objectType = "player"
    physics.addBody ( p, "dynamic", playerBodyElement )
    p.isSleepingAllowed = false

    return p
end

This change will add the player object to the physics simulation. The playerCollisionFilter defines which objects the player will collide with. We will add these objects below. The isBullet and isSleepingAllowed properties determine how the physics body will be treated in the simulation. You can read the physics engine documentation to learn more.

The objectType property will be used by us later. Do not worry about it yet.

Run the code in the simulator and watch what happens. What the heck? The player falls off the screen, although we have not written any new animation code. This is because we added the player to the physics simulation. For each object in the simulation a mass property gets calculated. And the default gravity vector is set to point to the bottom of the screen by default. The physics simulation will automatically update the position of the objects under simulation. Therefore, the player is dropping like real world objects would do. To fix this we will change the gravity vector. Add the following two lines to the bottom of the main.lua file:

-- Overhead view, like looking at a pool table from above.
physics.setGravity( 0, 0 )

Collision Detection

So far there are no objects the player could collide with. So let us add two new objects – one green and one blue square. Our goal is to detect when the player collides with one of the squares. This will look like the following screen shot.

Part3

When the player collides with the green square we will increase the score. When the player collides with the blue square we will decrease the score. As you can imagine, detecting collisions is a non-trivial task. It is even harder if objects are rotating. But as pointed out above, Box2d will do the heavy lifting for us. Now let us see how this can be done:

local function spawn( objectType, x, y )
    local object
    local sizeXY = math.random( 20, 100 )
    local collisionFilter = { categoryBits = 4, maskBits = 2 } -- collides with player only
    local body = { filter=collisionFilter, isSensor=true }
    object = display.newRect(  x, y, sizeXY, sizeXY )
    if "food" == objectType then
        object:setFillColor ( 0, 255, 0 )
    else
        object:setFillColor ( 0, 0, 255 )
    end
    object.objectType = objectType
    physics.addBody ( object, body )
    object.isFixedRotation = true
    return object
end

local green = spawn( "food", 50, 50 )
local blue = spawn( "enemy", 50, 200 )

Just add the above code at the bottom of the main.lua file. The spawn function creates a new rectangle and positions it at the coordinates given by x and y. The size of the rect will be randomized. Again we add this object to the physics simulation with physics.addBody. We define a collisionFilter for the physics body as we have done for the player object. The important part here is the maskBits = 2. This basically says that we want to detect collisions with objects that have the categoryBits set to 2: the player. Read more on collision detection in the Corona SDK docs.

When we run the code now we will see the two rectangles. But when we move the player to one of the rectangles nothing happens. The score label is not changing. The collision is already detected by the simulation and collision events are generated. But we do not listen for these events. Nor do we have code to handle these events. Listening to collision events is similar to listening to touch events. Add these lines to the bottom of your main.lua file:

-- We want to get notified when a collision occurs
local function onCollision( event )
    local type1 = event.object1.objectType
    local type2 = event.object2.objectType
    print("collision between " .. type1 .. " and " .. type2)
    if type1 == "food" or type2 == "food" then
        score = score + 1
    else
        score = score - 1
    end
    scoreLabel.text = score
end

Runtime:addEventListener ( "collision", onCollision )

As you can see we now have a handler for collision events called onCollision. We register this handler for collision events with the addEventListener function.

You will also notice that we finally used the objectType we added to the player and the two colored rectangles. Through this we can easily see what objects have collided.

Conclusion

We covered all parts of a simple game: the player, objects to collect, objects to avoid, and a score label. Some parts are missing though. We need a menu to start the game and we need the fun – the game logic. This is what we will add in the next part, which will be the last part of this tutorial series.

Download the code for this Corona SDK tutorial. Try to change the color, position, and the direction of the player rotation. Play with the code.

As always, I would be glad to have you as a reader of my RSS feed or as a follower on Twitter. You also can find me on Google+.

Happy coding.

Share

{ 6 comments… read them below or add one }

Ricardo September 15, 2011 at 10:59 pm

Hi Joe, if you dont want a square but you want an image on the fat freddy project, what you should do?

Thanks for all your helpful Tutorials.

Mike March 22, 2012 at 3:35 am

Nice post…

I created a utility called to help generate collision filters quickly and copy/paste them into projects
http://labs.zanuka.com/collisionator3000XL/index.html

you tube demo:
http://youtu.be/Mg89_7jlrJc

enjoy,

Mike

Fahad April 3, 2012 at 9:48 am

I have the same question as @Ricardo

Luke September 1, 2012 at 9:16 am

Hi I’m having a problem with the coerceOnScreen function.
I’m hoping that you can find the issue.
Here is my code:
– Forces the object to stay within the visible screen bounds
local function coerceOnScreen( object )
if object.x display.viewableContentWidth – object.width then
object.x = display.viewableContentWidth – object.width
end
if object.y display.viewableContentHeight – object.height then
object.y = display.viewableContentHeight – object.height
end
end

player.y = event.y – player.y0
coerceOnScreen( player )
elseif “ended” == phase or “cancelled” == phase then

Luke September 1, 2012 at 9:17 am

Sorry the spaces didn’t come through

Abhinav June 2, 2013 at 8:13 am

Hello there,thanks much Sir Joe.
I have a problem though.Instead of having a black square(freddy),I want a sprite that I have made.How do I do this.
PS:I had changed the sprite and touch and all was fine.There was a problem with the Physics thing.My Background is diffrent too.Please help.Urgent!

Leave a Comment

Notify me of followup comments via e-mail. You can also subscribe without commenting.