Working with Display Objects and the Stage

All drawing that occurs on the screen is accomplished by creating DisplayObjects. Anything that appears on the screen is an instance of a DisplayObject.

Creating Display Objects

You don't actually create these objects directly. Instead, you create special kinds of DisplayObjects such as rectangles, circles, images, text, groups, etc.

These objects are all first-class citizens. You can change their position, rotate them, animate them, turn them into buttons, etc.

All of these objects share common properties and methods that are described in the Display Objects chapter of Corona SDK Language and API Reference.

All instances of DisplayObject can be treated like normal Lua tables. This means you can add your own properties to the object as long as they don't conflict with the names of DisplayObject’s predefined properties and method. The one exception is that you cannot index into a DisplayObject as an array using numerical indices.

Painter’s Model

DisplayObjects are drawn to the screen using the Painter's Model of drawing. The easiest way to think of this is to imagine an actual painting. Here, the paint you apply at the beginning is below the paint you apply later. Each successive brush stroke obscures the strokes that came before.

You can think of a DisplayObject as analogous to a brush stroke. When you create a DisplayObject, you are “painting” a new object over existing display objects. As you draw more objects to the screen, the objects you draw last will obscure the ones you drew before.

Display Hierarchy

To manage the order in which DisplayObjects are drawn, DisplayObjects are organized in a hierarchy. This hierarchy determines which objects appear above other objects.

Group Objects

The hierarchy is made possible by the existence of group objects. Group objects are a special kind of DisplayObject that can have children. Group objects make it possible to organize your drawing so that you can build relationships between objects. One example of a group is a menu that pops up or slides down. The "menu" group would hold graphics, text, and buttons -- all children of the group.

You can make any DisplayObject a child of a group. The children are organized in an array, so the first child (index 1) is below the next child, and so on; the last child is always above all its siblings. You insert objects into a group using the group:insert() object method and you access the children by indexing into the group with integer indices (e.g. group[1] ):

 
local square = display.newRect( 0, 0, 100, 100 )
local rect = display.newRect( 0, 0, 100, 100 )
local group = display.newGroup()
group:insert( square )
group:insert( rect )
assert( (group[1] == square) and (group[2] == rect) )

Stage Objects

Whenever you create a new object, it is implicitly added to a special group object that is at the top of the hierarchy. This group object is called the stage object (often referred to as the current stage). Every time you create a DisplayObject, it is implicitly added to the stage object. By default, it will add that object at the end of the child array and thus appear above all other child display objects. (Currently, there is only one stage in Corona. This stage represents the entire screen.) Once an object has been created it can be used as-is or moved to another group.

Moving Objects Forward and Backward

Unlike in real painting, the ordering of display objects is not set in stone; you can change the relative ordering of objects. The order in which a group object’s children are drawn is determined by the ordering of the children array. Using the group:insert() object method, you can reorder the position of an object within its parent group. Conceptually, you can think of it as reinserting the object into the same parent group:

 
local square = display.newRect( 0, 0, 100, 100 )        -- Red square is
square:setFillColor( 255, 0, 0 )                                                -- at the bottom.
local circle = display.newCircle( 80, 120, 50 ) -- Green circle is
circle:setFillColor( 0, 255, 0 )                                                -- in the middle.
local rect = display.newRect( 0, 0, 100, 100 )          -- Blue rect is
rect:setFillColor( 0, 0, 255 )                                          -- at the top.
 
-- square,circle,rect all have same parent
local parent = square.parent
 
-- Move to top. Siblings at higher indices are above those at lower ones
parent:insert( square )         -- same as parent:insert( parent.length, square)
 
-- Move below all other siblings
parent:insert( 1, circle )

Objects can also be moved within the group using object:toBack and object:toFront methods.

Drawing Cycle

The basic drawing model involves a cycle between executing Lua code and rendering objects in the display tree of the current stage object. During this cycle, the screen is only updated when objects in the display tree have changed. These changes occur by adding, removing, or changing properties of the child DisplayObjects.

This cycle may occur 30 or 60 times a second, depending on the frame rate setting in config.lua (see Frame rate control). At the beginning of each cycle, an "enterFrame" event is dispatched to any registered listeners in your Lua code. Once all listeners have finished executing, the screen is updated.

Screen Updates

The screen never updates while a block of your Lua code is executing. Therefore, if you modify a display object multiple times in a code block (e.g. the x position property), only the last change (e.g. the final value of x) will be reflected by the screen update.

Coordinates and Transforms

s

Coordinate spaces define the location in which all drawing occurs. The screen represents the base coordinate system for drawing. All content must eventually be specified relative to the origin of the screen.

Often, it is unwieldy to describe everything in terms of screen coordinates. Therefore, we introduce the concept of local coordinates. Every display object operates in their own local coordinate system. The heavy lifting of converting between a display object's local coordinates and the screen coordinates is done for you.

The process of translating between local coordinates and screen coordinates is made possible by mathematical transforms, or transforms, for short. Transforms convert coordinates from one space to another.

Coordinates

A Cartesian coordinate system (also known as a rectangular coordinate system) is used to define position. Unlike standard Cartesian coordinates, the origin of the screen is located at the top-left corner so that positive y-coordinate values extend downward (positive x-values extend to the right as usual). All screen coordinates are defined relative to this origin.

Local coordinates allow you to manipulate geometric properties of a display object such as rotation in a more intuitive fashion. Every display object has an origin relative to its parent's. This origin essentially defines the position of the display object (relative to its parent). For example, if variable r was a rectangle, then it's origin/local-position would be (r.xOrigin, r.yOrigin).

When and object is created, it's coordinates are relative to the current stage (main screen). Adding a touch listener to the object will return touch locations that correspond to the location of the object on the screen. When the object is added to a group (not the current stage), the object's coordinates are relative to the group and not the screen. The touch locations returned by the object listener will no longer correspond to the x,y properties of by the touched object. The object.contentBounds API will map the object's coordinates back to the screen's coordinates.

Every object also has a local reference (or registration) point about which transformations such as rotations occur. The reference point is defined by two numbers (in the case of the rectangle r, these are r.xReference, r.yReference) which specify the location of the reference point relative to the local origin. By default, the reference point is the same as the local origin, i.e. the two numbers of the reference point are (0, 0).

Changing Position of Objects

To change the position of a display object, you can either manipulate the ( xOrigin, yOrigin ) properties or the ( x, y ) properties — typically you do the latter because that’s the same point about which scales and rotations occur.

The ( xReference, yReference ) properties do not affect the position of a display object. Rather, they define where the reference point will be located, as this location affects scaling and rotation.

When an Display Object is created, the origin specifies the TopLeft corner of the object. After the object has been created, the reference point is now the center of the object and the x,y values will reference that point. The exception is display.newCircle, which is created with a center origin.

You can move the reference point back to the TopLeft with object:setReferencePoint( display.TopLeftReferencePoint )

If you read the x,y reference points, they will be relative to the x,yOrigin points (center of the object). When the reference point is Center, the reference point is 0,0. When the reference point is TopLeft, the reference point is -w/2,-h/2 (1/2 the object's width and height).

Transforms

Often, keeping track of transformations is a very error-prone and tricky business. This is because the order in which geometric transformations occurs determines the final position. For example, rotating an object and then scaling it (non-uniformly) will generate a different result from scaling that object first and then rotating.

To simplify things, we define an order of operations when transforming the object. These operations are all relative to the reference point of the display object. In this way, you are free to change the value of an object's position, rotation, and scale properties in any order you please; the resulting transformation remains consistent.

This transformation is calculated by applying geometric operations in the following order:

  1. Scale the display object about its reference point using ( object.xScale, object.yScale ).
  2. Rotate about the display object's reference point object.rotation degrees.
  3. Move the object's origin (not its reference point) relative to the parent's by ( object.x, object.y ) in local coordinates.

Note: the methods object:scale(), object:rotate(), and object:translate() merely change the value of the underlying geometric properties. The order in which you call them does not affect the final result, so you should not think of them as matrix transformations.

Object References

Because objects can be reordered in the hierarchy, using integer indices to access children of groups is fragile. If you move a child above its sibling, all integer indices have to be updated. A simple solution to this is to store child display objects as a property of the parent group. This makes it easier to access those objects later.

Let’s consider a situation where we have images for the sun and the planets of our solar system. We want to put them all under one group. In this example, we have a table listing all the files and we’ve created a group that we’ll store the image objects in.

 
local planetFiles = { sun="sun.png", mercury="mercury.png",
   venus="venus.png", earth="earth.png", mars="mars.png",
   jupiter="jupiter.png", saturn="saturn.png", neptune="neptune.png",
   uranus="uranus.png", pluto="pluto.png" }
 
local solarSystem = display.newGroup()

The next step is to create the image objects by iterating through the table planetFiles. We use a special iterator ipairs which will return both the property name and the image filename stored in the property of planetFiles. We use the filename to load the image; we use the property name to assign a property in group so we can easily refer to it later in the group without worrying about integer indices:

 
-- Loop through all the files, load the image, assign property in the group for key,file in pairs( planetFiles ) do
        -- key will be "sun", "mercury", etc.
        -- file will be "sun.png", "mercury.png", etc.
        local planet = display.newImage( file )
   solarSystem:insert( planet )
   solarSystem[key] = planet 
end
 
-- Afterwards:
-- solarSystem.sun will refer to the image object for "sun.png",
-- solarSystem.mercury will refer to the image object "mercury.png",
-- etc.

Keep in mind that if you want to remove one of these objects, you need to do two things. First, you need to remove it from the display hierarchy. Second, you need to set the corresponding property of the parent group to nil (see Variable References).

Removing Objects Properly

Because devices have limited resources, it is important to remove display objects from the display hierarchy when you no longer use them. This helps overall system performance by reducing memory consumption (especially images) and eliminates unnecessary drawing.

When you create a display object, it is by default added to the root object of the display hierarchy. This object is a special kind of group object known as the stage object.

To properly remove an object so it no longer renders on screen, you need to remove the object explicitly from its parent. This removes the object from the display hierarchy. This can be done in either of the two following ways:

 
image.parent:remove( image ) -- remove image from hierarchy
-- or --
image:removeSelf( ) -- same as above

However, this is not always sufficient to free the memory consumed by the image. To ensure that the image object is garbage collected properly, we need to eliminate all variable references to it as we will explain in the next section.

Note: Starting with build 316, removing a group with object:removSelf will remove the group and all it's children. This will free up all object listeners and texture memory used by the Display Objects in the group and convert the Display Object variables into simple Lua tables. Lua will garbage collect the remaining Lua variables if there are no other references to the those variables. (See the next section on Variable References.)

Variable References

Even though a display object has been removed from the hierarchy, there are situations in which the object continues to exist. In our above example, the parent group solarSystem stores references to the image objects for planets as properties. So even after removing an image from the display hierarchy, we still need to ensure that solarSystem no longer refers to the image. To do this, we set the property to nil (we call this nil’ing out the property).

 
local sun = solarSystem.sun
sun.parent:remove( sun ) -- remove image from hierarchy
solarSystem.sun = nil -- remove sun as a property of solarSystem

Generally speaking, if you inserted the display object as a table element (e.g. as a property of the table or as an array element), the display object will remain in existence even though it does not display to screen (see Object References). You have to nil out the property as in the above example.

Similarly, if other variables that point to the display object, the display object cannot be freed as long as those objects continue to exist. For example, global variables are never freed so if a global variable points to a display object, it will continue to exist even if it is not in the display hierarchy. Here, you should also set the global variable to nil when you no longer need it.

Another subtlety is when a function refers to a local variable outside its scope:

local sun = solarSystem.sun
 
function dimSun()
        sun.alpha = 0.5 -- sun was declared outside the function block
end

In this case, there is still an outstanding reference to the image object inside this function. Because this function is global, the image object must remain in existence. There are 2 solutions: make the function non-global (i.e. local) or change the function so that no variables outside the function block are referenced. The latter is preferable and is also more general in that it can be applied to any display object:

local sun = solarSystem.sun
 
function dim( object )
        object.alpha = 0.5
end

Common Pitfalls

A common mistake is to improperly remove all objects from a group. This typically happens when you write code that iterates through a group attempting to remove each child from the display hierarchy. It’s natural to iterate through the group in the forward direction. However, this can cause no end of confusion.

Continuing with our solar system example, consider the following where we attempt (incorrectly) to remove all the objects from the solar system.

for i=1,solarSystem.numChildren do
        local child = solarSystem[i]
        child.parent:remove( child )
end

The problem here is that we are modifying a collection (i.e. the group’s children array) as we iterate through that same collection. The result is we remove every other child. The easiest way to illustrate this is with a parallel example involving an array of integers:

local array = {1,2,3,4,5,6,7,8,9,10}
print( table.concat( array, " " ) ) --> 1 2 3 4 5 6 7 8 9 10
 
for i=1,#array do
        table.remove( array, i )
end
 
print( table.concat( array, " " ) ) --> 2 4 6 8 10

The fix is to iterate backwards.

for i=solarSystem.numChildren,1,-1 do
        local child = solarSystem[i]
        child.parent:remove( child )
end

Of course, this only ensures that all children have been removed from the display hierarchy; you still have to set all references to these display objects to nil. So in this example, we were merely trying to illustrate the highlight the pitfalls of iterating forward through the children of a group and modifying the group at the same time. A good implementation would also set the corresponding properties in solarSystem to nil for proper cleanup.

Replies

Viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
dgaedcke
User offline. Last seen 6 days 35 min ago. Offline
Joined: 17 Apr 2010

This document needs some additional clarification:
You first talk about, xOrigin, and then you talk about xReference.

Both of those definitions are clear.

Then you jump to this paragraph:
***********
Changing Position of Objects
To change the position of a display object, you can either manipulate the ( xOrigin, yOrigin ) properties or the ( x, y ) properties — typically you do the latter because that’s the same point about which scales and rotations occur.
***********

and begin speaking of x & y, with no definition of what they are, or how they related to the coordinates of the parent object, or to the local (origin and reference) coordinates just discussed. The paragraph IMPLIES that x & y are the SAME as xReference, yRef..., (eg that’s the same point about which scales and rotations occur) but it does not state it outright, nor does it clarify if you can use them interchangeably.

Thanks for improving these docs.

FrankS
User offline. Last seen 40 weeks 4 days ago. Offline
Joined: 7 Aug 2010

+1

It is still unclear to me how all those different coordinates are related and how they "change" when adding objects to a group.

Also, how the scaling works with respect to those different coordinates and in the different (group) coordinate system.

Some more examples would be greatly appreciated.

-Frank.

marcoconti83
User offline. Last seen 34 weeks 17 hours ago. Offline
Joined: 25 Jan 2011

I believe the second example in the "Object reference" is incorrect. You mention an iterator to loop through the planets, but the snippet has no loop, only an unmatched end. I believe the for loop has been mistyped as a part of the comment.
Marco

photiscta
User offline. Last seen 3 hours 4 min ago. Offline
Joined: 16 Feb 2011

Hello!
I am using image/buttons with director to move from one page to other.
Using the display.remove seems not to work propertly, because system.getInfo( "textureMemoryUsed" ) prints the same amount of memeory.
What I am doing wrong? I tried to use it inside and outside the if - end.

e.x.
local arrowback = ui.newButton{
default = "arrow-back.png",
over = "arrow-back.png",
onEvent = PreviousPage,
id = "PreviousPageButton"
}
local function PreviousPage ( event )
if event.phase == "release" then
director:changeScene("mainmenu")
display.remove( localgroup )
print("page01:",system.getInfo("textureMemoryUsed" ))
end
end

dgaedcke
User offline. Last seen 6 days 35 min ago. Offline
Joined: 17 Apr 2010

With the exception of when you are trying to REMOVE items from a group, you should clarify whether using iPairs(group) is a supported method to iterate the children.

Also, in the example above where it says:

The next step is to create the image objects by iterating through the table planetFiles. We use a special iterator ipairs which will return both the property name and the image filename stored in the property of planetFiles.

that should be "pairs" and not ipairs....

Frank A.'s picture
Frank A.
User offline. Last seen 8 weeks 3 days ago. Offline
Joined: 25 Jan 2011

Hello all,

Is there any good detailed document to explain (hopefully with an example) how these coordinates affect each other?
I am working on my first project and I am facing problems when trying to remove an object from the group (using a touch listener) while maintaining it's same screen location.

t <-- is my object that receives the touch event and I want it to be removed from its current group but stays at the same screen coordinates!

If I use "t:removeSelf()" it will disappear from the screen, hence, I am inserting it into the stage object using:
display.getCurrentStage():insert( t )
Which works perfectly but the coordinates will be wrong, so I added:
t.x = event.x
t.y = event.y
It gives my object the same coordinates of the touch event when it first occurred, which will cause a little jump that I am trying to solve!

Any help will be highly appreciated.
Thanks
Frank A.