Gaming Your Way

May contain nuts.

Rot: PixiJS Depth sorting and performance

I thought I'd do a techy post for a change, it's been a while hasn't it ?

If you've played the demo or even just seen screenshots you'll know Rot is an isometric game.

God damn that tilt shift filter is good

Because the game was always destined for mobile performance is a key issue.

Depth sorting is always painful to do, to do it correctly you're looking at doing a topological sort ( See this excellent blog post for more details: https://mazebert.com/2013/04/18/isometric-depth-sorting/ ) which is fine if you're not running too many sprites, but less than great if you're looking at a silly number of particles, and Rot is all about a silly number of particles.

Let's backtrack a little. The display is made up of various play fields ( Layers ), the background is burned into one large sprite, on top of that we have the sprites play field ( i.e. all the zombies, particles, pretty much everything moving ) and then a fore ground play field ( There are more, but let's not make this any more complicated than it needs to be ).

Because of having the background / foreground laid out like that we don't have to depth sort them at all, we only have to worry about the sprites themselves.

I added an extra property to each sprite, isoZ. This is just a simple old school depth sorting formula I'm sure you're familiar with, based on the sprites x/y position ( For more details see the link above, or pretty much any tutorial about iso ) and gives us something to sort by.

Along with that property we also added a new method to the DisplayObjectContainer class, zSort(). This method loops through all the children in our sprite play field and tests if they're visible or not, if they are they get shoved into a new visibleChildren array. This is because we only want to sort sprites we actually care about, visible ones.
We learned a lot about performance working on DN8 for iPad, and one thing was to not add / remove children when they were needed, there's a large overhead for that, so when we start a level we add nearly all the sprites at once so they're there ready and waiting for when we need them ( Things like explosions are added as needed because in the overall scheme of things they're pretty rare, but zombies and blood are always in use ).


So we have over a 1000 sprites just sitting there waiting to do their thing, but by setting the visible property to false our zSort ignores them ( This is why we check for visible rather than just looping through all the children of the sprite play field ).

This improved performance a lot, the max number of sprites we sort is 512 but in testing even with multiple explosions going off it never hits over 350, well within out limits.

The actual sort code is simple as it's possible to be,

    this.visibleChildren.sort(function(a,b) {
        if (a.isoZ > b.isoZ){
            return -1;
        }
       return 1;
    });

That's it, we don't even really care if two sprites have the same z depth, we're using large numbers for the isoZ value so it's a little unlikely and even if it does happen, it'll be for a 60th of a second. I can live with that.

Now if you've played with doing iso games before you'll know this way can lead to clipping, where the depth sort is a fairly cheap and therefore not as good as doing a topological sort.
Yep.
The objective was to make the depth sorting good enough, not perfect. We lose a little accuracy, which hopefully you don't notice when playing ( You will now though ) in return for performance.

So cool, we've pulled out all the visible sprites and sorted them, job done...
( This is my Colombo moment )
... but there's one more thing.

When you look at any sort of display graph, the containers ( Be it a DisplayObjectContainer in pixiJS or the Stage in Flash ) are pretty much just a list of the children. To display them the plotter simply loops through the list plotting one at a time, much like a painter would ( Ah, that's why it's called the painters algorithm, it finally makes sense ).
But we've got a sorted list of only visible sprites, with a little re-writing we can remove some of the checks in pixiJS's plotter loop, we already know all these sprites should be plotted. Further more we can also check to see if the sprite has any children itself, the way the game is set up they usually don't, and if not avoid some extra looping too.

To recap, by doing a simple visibility test before sorting and then using that data we can really improve performance. The zSort method was around the 3rd most expensive function call when profiling the game, that's dropped right down now.

Squize.
Comments are closed