Skip to main content

Organizing display objects in layers in Corona SDK


Working with display objects and display groups in Corona SDK is fairly straightforward. Every Corona developer probably knows the basics of this and how you can organize your display objects in a hierarchy of display groups. All this is also pretty well documented by Corona Labs right here, so I'm not gonna go into details about any of this. Instead I'll show you how I've created a simple helper class called DisplayManager which I find very useful when it comes to leverage the concept of layers and to keep track of all display objects to avoid creating memory leaks by not cleaning up the display objects properly.

What I want the DisplayManager class to help me with is basically a few things, that otherwise requires quite a lot of attention when coding:

  • Leverage the concept of visual layers, and encapsulate functionality concerning layers in an object-oriented way, making it easy to extend when needed.
  • Make sure that all display objects are cleaned up as they should when necessary. This is very easy to get wrong if you don't have your display groups and objects neatly organized, often leading to memory leaks and very tedious bug hunts.
  • Clip content to the bounds of the specified display size using a container. DisplayManager handles this for you.
  • Eliminate the need to keep separate references to each display group in your code. Many times you're not interested in changing any of the objects in a particular group or layer, for example when having a background layer with static graphics. The only  need to keep a reference to a group like this is often to be able to clean it up when not needed anymore.
  • Be able to have easy access to the same layers from several modules and/or scenes. With DisplayManager you can just inject the same single instance anywhere you need it. No need for global variables or passing lots of display groups as parameters between functions.
  • Make sure that the class works well together with the composer module.
The DisplayManager class presented below contains only the most important functions to meet the criteria above. You start by creating a new instance with a number of predefined layers. After that you can insert display objects and display groups into any of these layers. When a layer is not needed anymore, just clean it up which will remove all objects and groups within that layer. There's also a convenience function to clean up all layers at once.

It's also possible to get a reference to a layer (which is really only a display group) to be able to inspect or change it anyway you want. This is actually not very nice from an object-oriented point of view. It would be cleaner to encapsulate the additional functionality within the DisplayManager class, but for simplicity it's often easier and much quicker to just get the layer and do whatever you need to do.

You can also get a reference to the main display container, i.e. the container that holds all the layers together and handles the content clipping. This is especially useful when working with composer scenes. Just get the display container and insert it into the scene's view and everything should work right away. This is also shown in the example code below.

If you run the example from main.lua you should see an output similar to this:


Just for demonstration purposes, the example code does the following:

  • Creates a scene using composer, to show how easy it is to integrate DisplayManager with a scene
  • Creates a DisplayManager with three layers: background, circles and squares
  • Adds some graphics to each layer. The objects are added one at a time to each layer, just to show that they'll end up in the correct visual order anyway.
  • The circles are divided into separate display groups, where the blue layer is scaled and repositioned. Again, just to demonstrate that the DisplayManager doesn't restrict your from working with display groups anyway you normally would.
  • One second after the scene is shown, the big green rect is added to the background layer.
  • After another second, the circle layer is cleaned up.
The DisplayManager class is not rocket science in any way. It's actually a very simple implementation, but it has saved me from a lot of headache during the making of my two Corona games. I realized the need for a class like this when creating Dragonflies. It turned out really well for me, so when I started working on Ice Trap it was a no-brainer to include it in my project. Hopefully it can now also come to good use to some of you fellow Corona SDK game programmers out there.


--[[
Corona SDK display object manager class
Use the DisplayManager class to keep track of all your display objects and organize them in layers.
Reduces problems related to making sure that all display objects are properly cleaned up.
Graphics that end up outside of the predefined display area are clipped using a container.
Example usage:
local dm = require("DisplayManager").new({ "background", "enemies", "player", "foreground" })
dm:insert("enemies", enemy1)
dm:insert("enemies", enemy2)
dm:insert("player", playerSprite)
dm:insert("background", bgImage)
dm:cleanUpLayer("enemies")
dm:cleanUp()
Markus Ranner 2016
--]]
local DisplayManager = {}
DisplayManager.__index = DisplayManager
-- Creates the main display container, that clips all graphics that end up outside of the display area
local function createDisplayContainer()
local displayContainer = display.newContainer( display.contentWidth, display.contentHeight)
displayContainer.x = display.contentCenterX
displayContainer.y = display.contentCenterY
return displayContainer
end
local function createLayers(layerNames, displayContainer)
local layers = {}
for index, layerName in ipairs(layerNames) do
local layer = display.newGroup()
-- Position the layer's center (0,0) at the top-left corner of the display
-- This is done to be able to position objects within the layers using coordinates in relation to the top left corner (0,0)
layer.x = -(displayContainer.x)
layer.y = -(displayContainer.y)
layers[layerName] = layer
displayContainer:insert(layer)
end
return layers
end
local function removeDisplayObject(displayObject)
-- Set a new state for the object that can be tested for elsewhere in the code
displayObject.state = "removed"
-- Remove the object itself
display.remove(displayObject)
end
local function cleanUpLayer( layer )
-- Keep the layers (display groups) to not have to recreate them, but remove all objects from them
if (layer and layer.numChildren) then
for i = layer.numChildren, 1, -1 do
local obj = layer[i]
removeDisplayObject(obj)
layer[i] = nil
end
end
end
-- PUBLIC CLASS METHODS -----------------------------------------
--[[
Gets the main display container from the DisplayManager. The display container holds all the layers.
--]]
function DisplayManager:getDisplayContainer()
return self.displayContainer
end
--[[
Gets a layer by name. The layer returned is just an ordinary display group.
--]]
function DisplayManager:getLayer(layerName)
return self.layers[layerName]
end
--[[
Inserts a display object into a specific layer. An error will be raised if the layer has not been defined when constructing the DisplayManager instance.
--]]
function DisplayManager:insert( layerName, displayObject )
local layer = self:getLayer(layerName)
if ( layer ) then
layer:insert(displayObject)
else
error("ERROR! A layer with specified name has not been configured: " .. layerName)
end
end
--[[
Cleans up a single layer, i.e removes all display objects within that particular layer
--]]
function DisplayManager:cleanUpLayer( layerName )
local layer = self:getLayer(layerName)
if ( layer ) then
cleanUpLayer( layer )
end
end
--[[
Removes all display objects in every layer
--]]
function DisplayManager:cleanUp()
for layerName, layer in pairs(self.layers) do
self:cleanUpLayer(layerName)
end
end
--[[
Create a new DisplayManager instance.
layerNames - A table of strings representing the layers to be managed and in which order they will appear visually. The first element will be in the back, and the last one in the front. For example { "background", "enemies", "player", "foreground" }
--]]
function DisplayManager.new(layerNames)
-- Create the main display container, that will hold all the layers and clip graphics that end up outside of the display area
local displayContainer = createDisplayContainer()
local newManager = {
displayContainer = displayContainer,
layers = createLayers(layerNames, displayContainer),
}
setmetatable(newManager, DisplayManager)
return newManager
end
return DisplayManager
--[[
Example demonstrating the DisplayManager class, using a simple composer scene
Markus Ranner 2016
--]]
local composer = require("composer")
display.setStatusBar(display.HiddenStatusBar)
composer.gotoScene("scene")
view raw main.lua hosted with ❤ by GitHub
local composer = require( "composer" )
local DisplayManager = require("DisplayManager")
local scene = composer.newScene()
-- Create a new DisplayManager instance with three layers
local dm = DisplayManager.new({ "background", "circles", "squares" })
local function setupGraphics()
-- Divide the circles into display groups, just to show that it also works to add display group to the DisplayManager's layers.
-- We scale and reposition the blue circle's group for demonstration purposes
local blueCircles = display.newGroup()
blueCircles.xScale = 0.5
blueCircles.yScale = 0.5
blueCircles.x = display.contentCenterX
blueCircles.y = display.contentCenterY
blueCircles.anchorX = 0.5
blueCircles.anchorY = 0.5
blueCircles.anchorChildren = true
local yellowCircles = display.newGroup()
-- Create 50 objects in each layer.
-- Note that the order in which objects are added to the different layers doesn't matter.
for i = 1, 50 do
local color = math.random(5, 10) / 10
local rectHeight = math.random(30, 100)
local bgRect = display.newRect(0, math.random(0, display.contentHeight), display.contentWidth, rectHeight)
bgRect.anchorX = 0
bgRect:setFillColor(color, color, color, 0.5)
dm:insert("background", bgRect)
local blueCircle = display.newCircle(math.random(0, display.contentWidth), math.random(0, display.contentHeight), math.random(20, 100))
blueCircle:setFillColor(0, 0, color, 1.0)
blueCircles:insert(blueCircle)
local yellowCircle = display.newCircle(math.random(0, display.contentWidth), math.random(0, display.contentHeight), math.random(20, 100))
yellowCircle:setFillColor(color, color, 0, 1.0)
yellowCircles:insert(yellowCircle)
local sideLength = math.random(20, 100)
local square = display.newRect(math.random(0, display.contentWidth), math.random(0, display.contentHeight), sideLength, sideLength)
square:setFillColor(color, 0, 0, 1.0)
dm:insert("squares", square)
end
dm:insert("circles", blueCircles)
dm:insert("circles", yellowCircles)
end
local function createAdditionalBackgroundGraphics()
local bigRect = display.newRect(200, 200, display.contentWidth - 400, display.contentHeight - 400)
bigRect:setFillColor(0, 0.5, 0, 1)
bigRect.anchorX = 0
bigRect.anchorY = 0
dm:insert("background", bigRect)
end
function scene:create( event )
-- Create some simple graphics in all three layers
setupGraphics()
-- Simply get the main display container from the DisplayManager and insert it into the scene's view
self.view:insert(dm:getDisplayContainer())
end
function scene:show( event )
if ( event.phase == "will" ) then
-- Just to demonstrate that the layers work, we add some extra graphics to the background here
-- A green rect should appear under the circles and squares
timer.performWithDelay(1000, function()
createAdditionalBackgroundGraphics()
end)
-- And then we remove the circles completely
timer.performWithDelay(2000, function()
dm:cleanUpLayer("circles")
end)
elseif ( event.phase == "did" ) then
end
end
function scene:hide( event )
if ( event.phase == "will" ) then
elseif ( event.phase == "did" ) then
end
end
function scene:destroy( event )
-- Clean up all layers at once. This removes all display objects that have been inserted into the DisplayManager
dm:cleanUp()
end
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )
return scene
view raw scene.lua hosted with ❤ by GitHub

Comments