Z ordering of sprites in Tiled maps

Wow it's been a while since I last posted, so this is long overdue. The past few months have flown by while I work on an Android project powered by Gameplay3d, which is a great (although, some may argue, incomplete - definitely worth checking out the 'next' branch) cross-platform framework, that has allowed me to really get get into mobile development. Oh and there was ChufJS, an incomplete and buggy scene graph written in Javascript/WebGL, which fun to make nonetheless. This post is about neither of those, though, what I'd like to do is address a Tiled map question which is often asked. My Tiled map loader for SFML, while producing varied results, is by far my most popular project, so it'd be remiss of me to not try and support it where I can. I should point out, of course, that these are by no means definitive answers and are hopefully flexible enough to be applied to tile maps in general, not just to the SFML Tiled map loader.
    So what's the deal? I've been asked on more than one occasion about the best way to have a player or other sprites move around on an RPG style map, and be able to walk both behind and in front of the scenery (aka z-ordering, or z-depth ordering). I can think of two options, one slightly more convoluted than the other, which I'll try to explain. Any code I use will be pseudo-code, although written with C++ in mind (the second example relies on the STL).

    The first example is based on what many professional games do, and actually relies on an optical illusion created with a carefully designed tile set. The map needs to be set out in three layers (although you could probably combine the bottom two) comprising of the background:


the scenery of which you want to walk in front:



and finally the top layer, the scenery behind which you'd you like the player to appear:



The grey grid is visible only in Tiled, which I left in the screenshots to help illustrate the point. Notice the roof tops and tree tops are quite small. This is key to the effect as they need to be smaller (or shorter, at least) than the player sprite:



(by the way, I'm using a Pokemon tile set sent to me quite a long time back, and I don't have the original source to give credit - if anyone knows please tell me so I can update this post appropriately.)

Next comes the important part: setting up the collision detection. Here I have drawn a set of boxes on a Tiled object layer and marked them in red for better visibility. I made sure the option to snap the objects to the Tiled grid was switched on. The SFML map loader has built in functions for automatically creating solid objects from object layers, which you can read about here. The player has two collision points placed by its feet (also in red):

 



Drawing the sprite above the building layer means that because the sprite's feet will never enter the collision box, the body will be able to pass over, or in front of any solid areas when approaching from below. On the other hand, if the sprite layer is behind the roof top layer, the sprite will go behind the buildings, but only as far as the collision points on the feet allow - preventing the illusion being broken by the player passing over the body of the house from the top, and also having the benefit of not allowing the sprite to get completely lost behind the house (I can only imagine losing your player on screen being rather frustrating). The end result is something like this:


In my opinion this is probably the most effective and programmatically cheap way of sorting sprite depth, and my personally preferred method.
   There is an option which can be performed in code, however, which I'll briefly touch upon. Firstly I'm assuming the use of C++ here (specifically C++11), although I'm sure other languages have similar features available. The only part of the map drawn in Tiled is the background: all of the detail and player sprites are stored in some sort of container, for argument's sake let's say a std::vector. This is already more computationally expensive because (in SFML at least) we have lost the use of vertex arrays, and using the default SFML sprite class will require an increase in draw calls, and incur a performance hit. This can be reduced a bit by culling all sprites not currently visible on screen, and placing the visible sprites in their own temporary vector.
    Once a list of sprites to be drawn is available, the STL provides sorting algorithms which can be used on the container, coupled with a functor or lambda expression, which will allow you to perform z-depth ordering on the sprites, before drawing each one. In pseudo-code it would look a bit like this:

...

std::vector<sprite*> spriteList = getVisibleSprites();

...

std::sort(spriteList.cbegin(), spriteList.cend(), [](const sprite* s1, const sprite* s2)->bool
    {
        return (s1->getPosition().y > s2->getPostion().y);
    })

...

background.draw();
for(const auto s : spriteList)
    s->draw();

Sprites are culled and placed into a container, sorted via a lambda expression which determines the sprites vertical position (if a sprite is lower on screen it should be drawn over the top of other sprites), and then each sprite is drawn over the background. The performance hit is usually negligible on modern hardware, but may become an issue on mobile devices.

    That pretty much sums up the two techniques, both of which I have employed in the past, although there are plenty of other techniques available via your favourite search engine. Hopefully this answers, to some extent at least, any questions people may have had about z-depth ordering in Tiled maps.

Comments

Popular Posts