Non-Physical Collisions

Corona includes a robust Box2D-powered physics engine featuring collision detection which can accomplish many things. It's even event-driven which means you can sense when collisions have started and ended, which two objects are involved in the collision, where they came into contact, and more. The only "catch" is that every colliding object must be a physics-enabled object under control of this physics engine which may be overkill if you're not using physics for other aspects of your game — for instance if all you need is basic knowledge of whether one simple object is occupying the same screen space as another.

Alternate Detection Methods

There are a few ways to detect collisions without using physics, including:

The first method can be complex if the rectangle has any rotation applied to it, and the separating axis theorem is fairly math-intensive, so neither of these will be covered in this tutorial. Instead, we'll discuss the overlapping circles and overlapping rectangles methods.

Note

Before considering non-physical collision detection, remember that if your objects are not basic rectangles or circles, the physics engine is almost certainly a better option. Using it, you can define complex polygon-based shapes, chain lines, multi-element constructions, and more. Basically, the two non-physical methods in this tutorial follow the "close enough" methodology, so if you need precise collisions, physics-based collisions are more appropriate.

Overlapping Circles

In some games, even objects that aren't artistically circular can often have their collision boundaries represented by a circle, for instance a circle that encloses the entire image or alternatively spans a slightly smaller area around the "center" of the object. During fast gameplay, the player probably won't notice exact shape-precise collision detection.

Some very simple calculations can determine if any two circles of arbitrary sizes are overlapping:

local function hasCollidedCircle( obj1, obj2 )

    if ( obj1 == nil ) then  -- Make sure the first object exists
        return false
    end
    if ( obj2 == nil ) then  -- Make sure the other object exists
        return false
    end

    local dx = obj1.x - obj2.x
    local dy = obj1.y - obj2.y

    local distance = math.sqrt( dx*dx + dy*dy )
    local objectSize = (obj2.contentWidth/2) + (obj1.contentWidth/2)

    if ( distance < objectSize ) then
        return true
    end
    return false
end

For this function, we simply pass in two display objects, obj1 and obj2. Using the contentWidth property to determine their width, we check if the two circles are closer than the distance of their centers and, if so, we know that they're touching.

Overlapping Rectangles

Overlapping rectangles are even easier to detect, using Corona's built-in object contentBounds properties:

local function hasCollidedRect( obj1, obj2 )

    if ( obj1 == nil ) then  -- Make sure the first object exists
        return false
    end
    if ( obj2 == nil ) then  -- Make sure the other object exists
        return false
    end

    local left = obj1.contentBounds.xMin <= obj2.contentBounds.xMin and obj1.contentBounds.xMax >= obj2.contentBounds.xMin
    local right = obj1.contentBounds.xMin >= obj2.contentBounds.xMin and obj1.contentBounds.xMin <= obj2.contentBounds.xMax
    local up = obj1.contentBounds.yMin <= obj2.contentBounds.yMin and obj1.contentBounds.yMax >= obj2.contentBounds.yMin
    local down = obj1.contentBounds.yMin >= obj2.contentBounds.yMin and obj1.contentBounds.yMin <= obj2.contentBounds.yMax

    return ( left or right ) and ( up or down )
end

This function works great for square or rectangular objects like tiles and cards. It uses a set of if statement checks to see if any corner of one rectangle is inside the bounds of the other rectangle.

Important

For images that have some transparency around them, note that this will include the transparent areas since the function uses the content bounds of those images. If necessary, you can adjust the math in the comparisons, for instance add or subtract a pixel value to/from the various "min" and "max" properties to account for the width of the transparency.

Checking for Collisions

Now that you have a couple different ways to determine if two items have collided, how can you use them? Unlike physics-based collisions, there are not collision listener events where the system tells you exactly when objects collide. Instead, you must check periodically (or frequently) in your own code.

Perhaps the most obvious method is to use an "enterFrame" Runtime listener as follows:

Consider this example function which assumes that you have a myPlayer object and multiple coin objects stored in a table named coinCache. Assuming the player can also use circle-based detection, this function loops through the coinCache table on each frame and checks if the player has come into contact with any coins:

local function gameLoop( event )

    for i = 1,#coinCache do
        -- Check for collision between player and this coin instance
        if ( coinCache[i] and hasCollidedCircle( myPlayer, coinCache[i] ) ) then
            -- Remove the coin from the screen
            display.remove( coinCache[i] )
            -- Remove reference from table
            coinCache[i] = nil
            -- Handle other collision aspects like increasing score, etc.
        end
    end
    return true
end
Runtime:addEventListener( "enterFrame", gameLoop )

Conclusion

That concludes our tutorial on non-physical collision detection. As you can see, this is a convenient method when you're building an app which requires basic collision detection and the physics engine — as powerful as it can be — is beyond the required needs of your tasks.

Character art in this tutorial is courtesy of Kenney. Kenney game studio supports other developers by creating free game assets and high quality learning material.