GMS 2.3+ Super Simple Depth Sorting

hippyman

Member
GM Version: GMS 2.3+
Target Platform: All
Download: https://www.dropbox.com/s/hl5weo10e94j3k2/EasyDepthSolution.yyz?dl=0

Summary:
I'm sure you've heard about the z-tilting technique. If you haven't you can find it here. While that technique is solid, I'd like to share my much simpler solution to get depth sorting like the old days. By only setting two flags at the start of your game and drawing with matrices. Don't worry if you're not comfortable with matrices. It's literally as simple as it could possibly be. No shaders. No camera setup. Five lines of code.

Tutorial:
Step 1: Enable z testing and alpha testing. These MUST be called FIRST in order for the next step to work. But you only ever have to call it one time at the very start of your game.
GML:
gpu_set_ztestenable(true);
gpu_set_alphatestenable(true);


Step 2: Any object that needs to be drawn needs to have the following code in their own draw event.
GML:
matrix_set(matrix_world, matrix_build(x,y,-y,0,0,image_angle,image_xscale,image_yscale,1));
draw_sprite_ext(sprite_index,image_index,0,0,1,1,0,image_blend,image_alpha);
matrix_set(matrix_world, matrix_build_identity());
That's it. Now you've got the classic "depth = -y" sorting. You can now replace the z value of the built matrix with whatever depth value you want. Lower values are drawn in front.

depthsort.gif

Explanation: (I'm not a professor or anything but I'll try my best.)
Basically GMS uses a 3D position in their default shader. But the z value is ignored unless you enable z-testing. So you turn on z-testing and draw the sprites and nothing changes. That's because the z value is always zero by default when the sprite gets drawn. So you have to supply the matrix_world matrix with a new matrix using a z value (that was a mouthful). I'm not going to get into the MVP matrix stuff. This is a good page to learn about that.

You can pop the first step's code into an object that initializes global stuff at the very start of your game.
You can put the code from the second step into a parent object and then any object that needs the same depth sorting can inherit from that parent object.

The nice thing about this system is nothing changes except for being able to sort by depth on the same layer regardless of instance order.
Since you're using the instance's built-in variables (x,y,image_angle,etc), all collision checking, scaling, rotating, etc. should work as expected.
 
Last edited:

gnysek

Member
As I have no opened GMS at the moment, there might be some bugs, but I've created replacements to top 3 most used draw_sprite functions:

GML:
function draw_self_sorted() {
    draw_sprite_sorted_ext(sprite_index, image_index, x, y, image_xscale, image_yscale, image_angle, image_blend, draw_get_alpha());
}

function draw_sprite_sorted(sprite_index, image_index, xx, yy) {
    draw_sprite_sorted_ext(sprite_index, image_index, xx, yy, 1, 1, 0, draw_get_color(), draw_get_alpha());
}

function draw_sprite_sorted_ext(sprite_index, image_index, xx, yy, image_xscale, image_yscale, image_angle, color, alpha) {
    matrix_set(matrix_world, matrix_build(xx, yy, -yy, 0, 0, image_angle, image_xscale, image_yscale, 1));
    draw_sprite_ext(sprite_index, image_index, 0, 0, 1, 1, 0, color, alpha);
    matrix_set(matrix_world, matrix_build_identity());
}

I would be interested if someone gonna make a performance test compared to other solutions.

I'm also not sure how to deal with "asset" layer, where we have sprites, thus it's rather not possible to attach such code to them. The only solution that comes fast to my mind is to put matrix_set(... code in layer_script_start, with 0,0 as x/y and 1 as scale, and then matrix_end() in layer_script_end in last sortable layer. Would it work then? I'm not good at matrices sadly :/ However I'm using sorting now in my game, and that seems as better solution (as for now I've used sorting over layers, making one layer per every 25 vertical pixels, but that causes some issues for games that doesn't align characters to grid).
 

hippyman

Member
I never actually tested rotation and scaling but you're totally right. I forgot that the regular draw_sprite function uses the angle/scale variables so it would double down on each of those. I'm going to fix the example at the top. Good catch!

I'm also not sure how to deal with "asset" layer, where we have sprites, thus it's rather not possible to attach such code to them.
I've never really messed with the asset layer. That gives me something to jack around with today. I'll come back with what I find out.
 

gnysek

Member
I've never really messed with the asset layer. That gives me something to jack around with today. I'll come back with what I find out.
It's worth to add, that when game runs, GMS allows putting every kind of asset (sprite/object/background/sequence) on any layer, so types are valid only in room editor - there's no layer type when game runs.
 

hippyman

Member
It's worth to add, that when game runs, GMS allows putting every kind of asset (sprite/object/background/sequence) on any layer, so types are valid only in room editor - there's no layer type when game runs.
I did mess around with it for a little bit yesterday and couldn't find a solution that wouldn't involve a controller object that essentially stores all the sprites in an array and deletes them from the room so the controller can draw them individually. I tried making a simple shader that would basically take the input position and rather than use the Z value it just put -Y where Z would have been but no luck. If there was a way to modify the world matrix before each sprite is drawn it would be possible but at the moment the only thing you can do is apply a matrix to the entire layer that the sprite is on which obviously won't work.

I guess the downside to this technique is it will only work with objects. I'm not sure how it would be possible with sprites, sequences, or tiles. If you happen to come up with some kind of solution then I would be all ears.
 
My issue with this is changing the world matrix for multiple objects (1000+) will lag the game. Every time you change the world matrix, you create a new vertex batch, which lags the game. The z-tilting technique of Ariak is more efficient in that regard. GameMaker is smart that as long as the shader, blendmode, blend color and matrix did not change, all sprites will be batched together.
 
Top