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...
- partition priority
- render tables
-- 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 )
insertPropBy 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(!)
Smallville teaches us only Bizarro Clark wears blue over red. To fix things, we have to call an additional function for each prop.-- 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 )
Super! It works. We'll revisit priority in just a bit.-- 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 )
LayeringIf 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.
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.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 )
|Dawn in split into several layers|
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 )