Rendering order of groups children

Yeah, that’s a great way to handle it.

Just a clarification with my code:

There can be any number of objects with the same “z-index”. You can also decide to insert the new object either behind or in front (default) of all objects within the layer.  *And* you don’t have to re-insert the objects every frame. Just insert any new object in any “z-index” layer and it will automatically be in the right place.

@ingemer I’m probably missing something, I’m looking at your code but I do not undetstand how it handles moving objects? if one object passes another in the y axis how when are the z-indices fixed?

Sorry, the code is a bit messy. I wrote it hastily to test a theory I had on how to make layers work.

(To see it in action you can copy-paste the code above into a main.lua)

I’ve stripped away some code below to emphasize the main concept

-- create a top-level group to hold all layers local z = display.newGroup() -- get a random "z-index" local zIndex = random(MAX\_LAYERS); -- create a rect and a text object local o = display.newRect(0, 0, 40, 40); local t = display.newText(zIndex, 0, 0, native.systemFont, 10) -- group the above objects together and create a zIndex property for the group local g = display.newGroup(); g:insert(o); g:insert(t); g.y = zIndex \* 20; g.zIndex = zIndex; -- insert the group into the z group z:insert(makeIndex(zIndex), g);

It’s the last line that does the “magic”. 

Now for a detailed (and hopefully comprehensible :)) explanation of what I’m doing:

Each display object created by corona has its own display order much like a FIFO. The objects will be rendered in sequential order. First-In, First-Out.

To emulate a layer hierarchy I create a top level z-group to hold all layers. Corona allows you to insert a display object within a display group at a certain index, effectively allowing you to insert new objects behind/in-front of older ones. The problem is that as objects are created/destroyed you have no easy way of knowing which index to use.

To solve this problem, this first thing I do is to create invisible place-holder objects who’s only purpose is to act as markers for where layers begin within the z-group. Every time a new object is inserted into a layer I call makeIndex() which returns the correct index to where the new object is to be inserted. Each layer has a minIndex property to keep track of which index the layer starts with (pointer to the place-holder object).

As an example: With 10 layers, 10 placeholder objects will be created with indices 1-10

Each layer’s minIndex will then be as follows:

z.minIndex[1] = 1 (placeholder object position)

z.minIndex[2] = 2 

z.minIndex[3] = 3 

z.minIndex[4] = 4 

z.minIndex[5] = 5 

z.minIndex[6] = 6 

z.minIndex[7] = 7 

z.minIndex[8] = 8 

z.minIndex[9] = 9 

z.minIndex[10] = 10 

If an object is inserted into layer 5, makeIndex(5) will +1 minIndex for layers 6 to 10 and return 6 (placeholder + 1).
z.minIndex[1] = 1 
z.minIndex[2] = 2 
z.minIndex[3] = 3 
z.minIndex[4] = 4 
z.minIndex[5] = 5 
z.minIndex[6] = 7 
z.minIndex[7] = 8 
z.minIndex[8] = 9 
z.minIndex[9] = 10 
z.minIndex[10] = 11 

If an object is later inserted into layer 9, makeIndex(9) will +1 minIndex for layer 10 and return 11.
z.minIndex[1] = 1
z.minIndex[2] = 2
z.minIndex[3] = 3
z.minIndex[4] = 4
z.minIndex[5] = 5
z.minIndex[6] = 7
z.minIndex[7] = 8
z.minIndex[8] = 9
z.minIndex[9] = 10
z.minIndex[10] = 12

If a second object is later inserted into layer 5, makeIndex(5, “bottom”) will +1 minIndex for layers 6 to 10 and return 6. makeIndex(5, “top”) will return 7 (“top” is the default if omitted).

z.minIndex[1] = 1
z.minIndex[2] = 2
z.minIndex[3] = 3
z.minIndex[4] = 4
z.minIndex[5] = 5
z.minIndex[6] = 8
z.minIndex[7] = 9
z.minIndex[8] = 10
z.minIndex[9] = 11
z.minIndex[10] = 13

Also, before removing an object the function removeIndex(object.zIndex) must be called so that minIndex for each layer is updated properly. 

I hope this makes some sense…

I realized that my original sample code only worked with Graphics 2.

I’ve updated it to work with both G1 and G2 now.

Darn I wish I had time to have a stab at knocking up some code, you guys have got me rather interested :slight_smile:

Still, great work :slight_smile:

OK, now I’ve modularized it a bit to keep the logic separated. Give it a whirl and say what you think.

Now there’s a layer.lua with the layer logic and main.lua with program logic.

-- -- layer.lua -- local M = {}; local z; -- z-index display group local MAX\_LAYERS -- max number of layers -- helper functions to keep track of where current z-index is local makeIndex = function(i, order) order = order or "top"; if (i \< MAX\_LAYERS) then -- push indexes up for j = i + 1, MAX\_LAYERS do z.minIndex[j] = z.minIndex[j] + 1; end end local retVal; if (order == "top") then if (i == MAX\_LAYERS) then retVal = z.numChildren + 1; else retVal = z.minIndex[i + 1] - 1; end else -- assume "bottom" retVal = z.minIndex[i] + 1; end return retVal; end M.insertObject = function(object, order) if (not object.zIndex) then print("LAYER: Display object is missing a zIndex property"); return; end z:insert(makeIndex(object.zIndex, order), object) end M.removeObject = function(object, options) options = options or {}; local i = object.zIndex; if (i \< MAX\_LAYERS) then -- pop indexes down for j = i + 1, MAX\_LAYERS do z.minIndex[j] = z.minIndex[j] - 1; end end if (not options.keepObject) then object:removeSelf(); end end M.changeIndex = function(object, newIndex, order) M.removeObject(object, {keepObject=true}); object.zIndex = newIndex; M.insertObject(object, order); end M.init = function(maxLayers) if (not maxLayers) then print("LAYER init(): Must specify number of layers") return; end MAX\_LAYERS = maxLayers; z = display.newGroup(); z.minIndex = {}; for i = 1, MAX\_LAYERS do local r = display.newRect(z, 0, 0, 1, 1); -- z-index placeholder object r.isVisible = false; z.minIndex[i] = i; end return M; end return M;

-- -- main.lua -- local buildInfo = system.getInfo("build"); local buildVersion = tonumber(buildInfo:sub(buildInfo:find("%.")+1)); math.randomseed(os.time()); local random = math.random; local MAX\_LAYERS = 10; -- max # of layers local MAX\_OBJECTS = 100; -- max # of objects local travelTime; -- time to travel across screen local spawnObject; -- predefined function local layer = require("layer").init(MAX\_LAYERS); local onTap = function(event) local object = event.target; local newIndex = object.zIndex \> 1 and object.zIndex - 1 or MAX\_LAYERS; layer.changeIndex(object, newIndex); object.y = object.zIndex \* 20 + 20; return true; end spawnObject = function() local zIndex = random(MAX\_LAYERS); local o = display.newRect(0, 0, 40, 40); local t = display.newText(zIndex, 0, 0, native.systemFont, 10) if (buildVersion \> 2000) then -- Graphics 2.0 o:setFillColor(random(), random(), random()); t:setFillColor(0); else -- Graphics 1.0 o:setFillColor(random(255), random(255), random(255)); t:setTextColor(0); t:setReferencePoint(display.CenterReferencePoint); t.x, t.y = 20, 20; end local g = display.newGroup(); g:insert(o); g:insert(t); g.y = zIndex \* 20 + 20; g.zIndex = zIndex; g:addEventListener("tap", onTap) layer.insertObject(g); g.xScale, g.yScale = 0.01, 0.01; transition.to(g, {time=800, xScale=1.0, yScale=1.0, transition=easing.outElastic}); travelTime = 10000 + random(7000); transition.to(g, {time=travelTime, x=display.contentWidth, onComplete=function(o) transition.to(o, {time=200, alpha=0, xScale=0.01, yScale=0.01, onComplete=function(o) layer.removeObject(o); spawnObject(); -- create new object end}); end}); end -- create MAX\_OBJECTS objects with a random z-index for i = 1, MAX\_OBJECTS do spawnObject(); end &nbsp;

layer.lua exposes these functions:

init(maxLayers)

Must be called to initialize the module. maxLayers is the number of layers wanted

insertObject(object [, “top” | “bottom”])

Inserts a display object into a layer.   IMPORTANT : object must have a zIndex property set.

“top” will insert the object above all other objects within the layer.

“bottom” will insert the object under all other objects within the layer.

Defaults to “top” if omitted.

removeObject(object)

Removes an object

changeIndex(object, newIndex [, “top” | “bottom”])

Changes the zIndex of an object

main.lua creates 100 objects (multi-colored square boxes) each with a random zIndex. The boxes move across the screen and when they hit the edge they are removed and a new box is created with a random zIndex. Rinse and Repeat…

New feature: You can tap on any square to send it up to the previous layer.

This is all done without having to redraw the whole stack.

For fun, I did my take on it. You can find it here:

https://github.com/Rakoonic/Sprite-sorting

It works by requiring a file which extends the normal display object, so you end up with:

[lua]local sortableGroup = display.newSortableGroup()[/lua]

then you need to define how to set up the sorting, by either supplying your own sort function, or specifying a property of each of the children, and then a sort order.

Example of doing your own sort function:

[lua]sortableGroup:setSort( function( a, b ) return ( a.x + a.y ) < (b.x + b.y ) ; end )[/lua]

To specify a property and a sort direction you’d do:

[lua]sortableGroup:setSort{ property = “y”, direction = “bigger=nearer” }[/lua]

where the property can be anything that all the children share (eg, x, width, alpha etc), and the direction specifies what values of that property are considered nearer to you or not. The two values for that are “bigger=nearer” and “smaller=nearer” - hopefully reasonably obvious :slight_smile:

Then in the future, once you have set up said sort function somehow, all you need to do is call:

[lua]sortableGroup:sort()[/lua]

and by the wonders of modern magic it will rearrange all the sprites in the group correctly.

The github library above has a working sample and a few varieties set up so you can quickly test tweaking the properties and direction etc.