Controlling Moai Rendering Order - Part 1

Using Moai, how can you ensure one object appears over top of another? Maybe you need a starfield to appear under your ships, perhaps you want to see through a translucent window to objects behind a building, or maybe you just need a dialog button to appear over its background.

Most 3d games use a z-buffer to record the depth of each pixel rendered, and rely on a z-test so that pixels closer to the viewer can take precedence over pixels further away. Many 2d games, on the other hand, rely on rendering order to control which objects display over what. Every new object rendered simply overdraws ( or blends on top of ) whatever is currently on the screen.

In Moai, by default, it's rendering order that controls appearance. How, then, do you control the rendering order of objects? In what follows, I'll explain four techniques you can use to get your scenes rendering how you want...


Four Techniques

  1. insertProp 
  2. layering
  3. partition priority
  4. render tables
The main renderable objects in Moai are called "props". ( You can see my first moai post, or moai's docs, for more info. ) The most common way to get them on screen is to first create a viewport and a layer, add the layer to the global render manager, then create the prop, and insert the prop into the layer.
-- create a viewport 
-- 0,0 will be at upper-left corner of the screen
local viewport = MOAIViewport.new ()
viewport:setSize ( 128, 128 )
viewport:setScale ( 128, -128 )
viewport:setOffset ( -1, 1 )

-- create a layer associated with that viewport
local layer = MOAILayer2D.new ()
layer:setViewport ( viewport )

-- add the layer to the rendermgr
MOAIRenderMgr.pushRenderPass ( layer )

-- create a 'deck', the visual appearance for a 'prop'
local deck = MOAIGfxQuad2D.new ()
deck:setTexture ( "white.png" )
deck:setRect ( 0, 0, 32, 32 )

-- create the prop
local prop = MOAIProp2D.new ()
prop:setDeck ( deck )

-- add the prop to the layer
layer:insertProp ( prop )

insertProp

By default, props are rendered in the order you add them to a layer. Objects you insert later appear over objects you inserted earlier. The simplest way to control rendering order, then, is to control the order you insert props. If you want to render ships over a starfield, just create and insert the starfield first. In many cases this works well because rendering order often arises naturally out of the execution order of your game.
local red= MOAIProp2D.new ()
red:setDeck ( deck )
red:setColor( 1,0,0 )
layer:insertProp ( red )

local blue= MOAIProp2D.new ()
blue:setDeck ( deck )
blue:setColor( 0,0,1 )
blue:setLoc( 16,16 )
layer:insertProp ( blue )

Sometimes, however, this natural order isn't enough. In Dawn, for instance, the world map is created first. There are four pre-defined groups in the map: the background tiles, the towns, the paths between towns, and a group containing player sprites and effects. As you progress through the game, new towns and paths are revealed. But, they need to appear underneath the existing player sprites.

It might be interesting if insertProp() allowed you to specify where in the series of existing props you want yours to be. But, the interface doesn't provide that sort of option.

An obvious technique would be to remove the sprites and re-add them. That result in a fair amount of maintenance work if you have a large number of props, but with the above snippet, it's relatively straightforward. Just note, for reasons I'll get into later, the following code doesn't actually work(!)
-- remove both props
layer:removeProp ( red )
layer:removeProp ( blue )

-- re-insert in the opposite order:
-- blue should get rendered first now,
-- but this doesn't actually work.
layer:insertProp ( blue )
layer:insertProp ( red )
Smallville teaches us only Bizarro Clark wears blue over red. To fix things, we have to call an additional function for each prop.
-- remove red and clear priority
layer:removeProp ( red )
red:setPriority()

-- remove blue and clear priority
layer:removeProp ( blue )
blue:setPriority() 

-- re-insert them now, and all is well.
layer:insertProp ( blue )
layer:insertProp ( red )
Super! It works. We'll revisit priority in just a bit.

Layering

If you look back at the first code snippet, you'll notice, in addition to the order of insertion, there's additional element available: the layer a prop goes in. Just as we can have multiple props, we can have multiple layers. And, just as the order of insertProp controls the order of rendering within a layer, the order of pushRenderPass controls the order of rendering between layers.
local redLayer = MOAILayer2D.new ()
redLayer:setViewport ( viewport )

local blueLayer= MOAILayer2D.new ()
blueLayer:setViewport ( viewport )

local red = MOAIProp2D.new ()
red:setColor ( 1,0,0 )
red:setDeck ( deck )
redLayer:insertProp( red )

local blue = MOAIProp2D.new ()
blue:setColor ( 0,0,1 )
blue:setLoc( 16,16 )
blue:setDeck ( deck )
blueLayer:insertProp( blue )

-- blue is pushed first, so it will render first
MOAIRenderMgr.pushRenderPass ( blueLayer )
-- red will render over top
MOAIRenderMgr.pushRenderPass ( redLayer )
In the scenario mentioned with Dawn, it would be easy to create separate layers for the world map, the paths, and the player spites. As long as the path layer gets inserted before the sprites layer, all is well.

Dawn in split into several layers
Just like with props, however, the order of the layers is fixed. The only way to control the ordering of the layers via the standard API is to remove a layer ( via removeRenderPass ) and re-add it.

In addition, it's worthwhile to note that layers are somewhat heavyweight. At the start of each layer's rendering, Moai resets the majority of the rendering device state. So, while layering is useful, it's worthwhile seeing what additional techniques are available. In part two of this post, I'll show you two more techniques: partition priority, and render tables towards that end.

One final note on layers: while MOAILayer inherits from MOAIProp -- which allows layers to have a consistent interface for position, scale, and color -- layers don't actually behave much like props. When a prop gets drawn, the prop renders its appearance using a deck. A layer, however, ignores any deck you may have assigned to it, and simply renders the props you've inserted. Likewise, while it's the deck that matters when computing the bounding box for a prop, it's the viewport that matters for the layer. And, finally, while the API allows you to insertProp one layer into another, this actually winds up crashing your game when the contained layer renders.
local layer1= MOAILayer2D.new ()
layer1:setViewport ( viewport )

local layer2= MOAILayer2D.new ()
layer2:setViewport ( viewport )

-- you can try hierarchical layers:
layer1:insertProp( layer2 )

-- but rendering will crash:
MOAIRenderMgr.pushRenderPass ( layer1 )

5 comments:

Unknown said...

Hello, I'm dying to see part #2, I'm converting a project leaning heavily on Corona groups and having trouble managing priority across a whole bunch of scene graph objects.

Unknown said...

By the way, your 275 fixed height iframe comments box makes it really hard to post comments (try it and see).

Unknown said...

Agree on the iframe comment box comment. Entering a CAPTCHA and pressing publish make for an "entertaining" session with the TAB key (which doesn't work at all in Chrome) ;)

ionous said...

Part 2 is up. I hope it's helpful! I'd like to learn more about Corona when I have some time. I've heard good things, but haven't tried it out.

Sorry about the comment box. I will investigate. This is blogger munged with some custom stylings from an old wordpress version, and it definitely needs some love!

ionous said...

i've done the easy thing: swapped over to full screen blogger comments. not particularly pretty, but functionality seems much better.

let me know if causes you trouble the next time round!