With Shadowrun on the horizon and a number of other isometric Moai SDK titles in the works, the best way to set up and render isometric scenes has been hot topic as of late.

Isometric is an interesting subject to tackle. There are a number of possible approaches, each with its own advantages and disadvantages. The contents of this article are strictly my own, humble observations. While I like to think that for any set of requirements there should be one ‘correct’ solution, it’s not my place to guess those requirements let alone dictate their solutions.

My design goal around isometric is to have Moai SDK maintain an ‘agnostic’ view of things and give each developer the opportunity to implement isometric in his or her preferred way. At the same time I want to offer a powerful set of tools (such as a real iso-sort) that may be used to implement isometric out of the box.

Isometric thoughts after the fold...

Setting up an Isometric Camera and Drawing a Tile Map

You can render an isometric view by using a ‘2D’ camera, rotating it 45 degrees and scaling the Y axis to squash square diamonds into isometric (or dimetric) proportions. Alternatively, you can use a 3D orthographic projection rotated on the X and Z axes:

 

camera = MOAICamera.new ()
camera:setOrtho ( true )
camera:setNearPlane ( 10000 )
camera:setFarPlane ( -10000 )
camera:setRot ( 60, 0, 45 )
layer:setCamera ( camera )

 

If you rotate your camera, you can actually use a regular MOAIGrid to render your tiles correctly. The trick is to render a rectangle in screen space but sample a diamond in texture space (assuming your tile set is actually diamond-shaped; more about this later):

 

tileDeck = MOAITileDeck2D.new ()
tileDeck:setTexture ( "diamond-tiles.png" )
tileDeck:setSize ( 4, 4 )
tileDeck:setUVQuad ( 0, 0.5, 0.5, 0, 0, -0.5, -0.5, 0 )

 

square-grid-iso

As you can see I use MOAITileDeck2D’s setUVQuad () to define a diamond shape. This sample isn’t perfect as my source tiles are square diamonds; in a game you would want to use isometric or dimetric source tiles and set up your UV coordinates accordingly. The goal is to render the tiles to the screen without any filtering.

Drawing Diamond-Shaped Tiles with a Rectangular Tile Map

Even though we want to render an isometric scene with the appearance of diamond-shaped tiles, the reality is that diamond-shaped tiles can be a pain for artists to work with. It’s much easier (and more efficient) to draw all the pieces of our scene using good old rectangular tiles.

Here’s a simple tile set texture based containing square tiles that may be used to construct a dimetric game map:

iso-grid

And here’s a set of wall brushes built from those tiles:

iso-walls

This is easy and intuitive for a map designer to understand and there are plenty of tools out (like Tiled) that can be used to create a brush set like the one above using rectangular tiles.

To render our scene, we need a way to select each wall shape from the brush set to be displayed individually. To do this we can use a MOAIGridDeck2D:

 

gridDeck = MOAIGridDeck2D.new ()
gridDeck:setGrid ( grid )
gridDeck:setDeck ( tileDeck )
gridDeck:reserveBrushes ( 8 )
gridDeck:setBrush ( 1,        0x01, 0x01,            2, 6,         -64, -192 )
gridDeck:setBrush ( 2,        0x03, 0x01,            2, 7,         -64, -224 )
gridDeck:setBrush ( 3,        0x03, 0x01,            4, 7,         -64, -224 )
gridDeck:setBrush ( 4,        0x05, 0x01,            2, 7,         0, -224 )
gridDeck:setBrush ( 5,        0x07, 0x01,            2, 6,         0, -192 )
gridDeck:setBrush ( 6,        0x09, 0x01,            4, 7,         -64, -224 )
gridDeck:setBrush ( 7,        0x0d, 0x01,            4, 7,         -64, -224 )
gridDeck:setBrush ( 8,        0x11, 0x01,            4, 6,         -64, -192 )

 

Each brush in the grid deck selects a sub-section of the associated tile map for rendering. Once our grid deck it set up, we can attach it to props and use it to render wall chunks. To get the brushes to render correctly in our isometric view, we can flag the props to render as billboards so that in spite of the skewed projection the wall brushes will render screen-aligned, just the way the artist intended them:

 

prop = MOAIProp.new ()
prop:setDeck ( gridDeck )
prop:setBillboard ( true )
layer:insertProp ( prop )

 

Sorting

If all of our brushes are symmetrical, a simple Y-sort will suffice. If we care about elevation, we can use the sort vector to compose a single sort key out of each object’s Y position combined with its Z position multiplied by a scalar.

If we plan to support asymmetrical brushes, sorting becomes more complex. In researching the problem I learned about two basic approaches.

The first is the ‘OpenSpace’ sort, which is tile-based. You can use an OpenSpace style sort by setting up the sort vector to use a combination of X and Y coordinates such that they correspond to tile coordinates. See the document here about placing 2D elements in the scene. The basic idea is to break up our brushes and control sorting by placement of their origin.

The other approach is the ‘Filmation’ sort. This sort treats brushes as 3D axis-aligned boxes and attempts to sort them based on their spatial relationships. In other words, when implementing a Filmation-like sorting algorithm, the position of each box relative to all other boxes must be considered.

As I mentioned, simple axis sorting and (by extension) OpenSpace style sorting is supported implicitly. Even though implementing a Filmation-style sort is more work, it appears to be a popular alternative and seems intuitive from a usability standpoint in that objects have real volume and, to the extent they’re never allowed to overlap in 3-space, sorting ‘just works’ as objects move around.

After some research I implemented a Filmation-style sort for Moai SDK. You can select it by using MOAILayer’s SORT_ISO constant like this:

 

layer = MOAILayer2D.new ()
layer:setSortMode ( MOAILayer.SORT_ISO )
MOAISim.pushRenderPass ( layer )

 

Here’s a screen shot showing some 3D boxes being sorted (from moai-dev/samples/iso/sort-iso):

iso-sort-boxes

Using 2D Billboards as 3D Objects

If you want iso-sort to correctly handle 2D billboards you will need to ‘trick’ it into thinking that the objects are 3D. As iso-sort uses the bounding box of the props to sort them, the billboard bounding box will not work. Fortunately, we can override the bounding box and provide our own 3D bounding box suitable for iso-sort.

To show what I mean, here’s a screen shot from the moai-dev/samples/iso/bounds-override showing a floor tile being assigned a (wildly inaccurate) square bounding box:

bounds-override

And here’s a code snippet in which we attach a MOAIBoundsDeck to a MOAIGfxQuad2D to override the bounds for index zero:

 

boundsDeck = MOAIBoundsDeck.new ()
boundsDeck:reserveBounds ( 1 )
boundsDeck:reserveIndices ( 1 )
boundsDeck:setBounds ( 1, -48, -48, -48, 48, 48, 48 )
gfxQuad = MOAIGfxQuad2D.new ()
gfxQuad:setTexture ( texture )
gfxQuad:setBoundsDeck ( boundsDeck )
gfxQuad:setRect ( -48, -48, 48, 48 )
gfxQuad:setUVRect ( 0, 0, 1, 1 )
prop = MOAIProp.new ()
prop:setDeck ( gfxQuad )
layer:insertProp ( prop )

 

The sample in moai-dev/samples/iso/iso-walls-brushes shows a more accurate set of bounding boxes applied to our wall brush billboards. Just click in the demo window to iterate over the brush set. The bounds overrides are rendered in white using MOAIDebugLines:

iso-walls-bounds

Some of these may look strange at first, but if you contemplate them you will understand.

Putting it all Together

Here’s a screen shot of my original proof of concept (moai-dev/samples/iso/iso-grid-brush). In it a 3D, textured box is rendered along with some 2D billboards. The bounding boxes are rendered in white. As long as none of the bounding boxes overlap (in 3-space) everything should sort correctly:

iso-billboard-brush-scene

And here’s a similar screen shot taken from moai-dev/samples/iso/iso-walls-thick in which some of the wall brushes from previous samples are used to construct a simple map in which our 3D box may roam about:

iso-billboard-wall-scene

The Future

In most of our samples 2D and 3D objects are combined in the same scene. As long as the 3D objects are convex there is no problem. With more detailed 3D objects (such as skinned characters) a Z-buffer will be required. To support the rendering of interleaved 3D objects with billboard we need to implement an object-by-object Z-buffer toggling, so self-intersecting 3D objects may be rendered with the Z-buffer on, whereas billboards will render with the Z-buffer off.

It’s also worth noting that MOAIProp is not the most lightweight of objects. As long as your partition is set up to use reasonable cell sizes then you should get decent performance thank to Moai SDK’s screen culling. Nonetheless, there is still a lot of extra data carried in each prop that isn’t needed to render static chunks of terrain. One approach would be to offer something like a ‘MOAIPropLite’ or ‘MOAIPointProp’ that doesn’t contain a full transform, just an index and an offset. Another approach would be to implement a new deck type that can layout objects indexed from other decks.

In terms of efficiency, iso-sort operates only on visible objects (after the screen cull). That said, the nature of the algorithm does require testing every currently visible object against every other, so try to minimize the number of objects if possible. Favor big, chunky walls, for example. Also, think about breaking your scene into multiple layers (floor, walls and characters, for example). Since the floor will always be drawn below the walls and characters, we shouldn’t have to sort it.

Conclusion

We know of at least a few isometric Moai SDK games in the works, but we (Zipline) aren’t working on anything isometric just right now. One of the first isometric titles you’ll be seeing in the near future does not (as of this writing) use the approach outlined above. This title was begun before iso-sort was available in Moai SDK. The developer instead juggles prop priorities on a tile-by-tile basis as characters move around, which seems to work pretty well.

I’ve shared the above ideas with some other developers starting isometric projects but the jury is still out as to whether they will use my ‘generalized’ approach or implement game-specific scene rendering via an extension. We’ll see.

One day we (Zipline) will begin an isometric title of our own and may further develop the ideas in this post. In the mean time, as you all know, Moai SDK is sort of a ‘game engine construction kit.’ If any interested readers wanted to write their own isometric toolkit based on what I’ve laid out here (or similar ideas) I’m sure you’d find no shortage of interested takers (and possible customers) to use it.