K
kevins_office
Guest
Object depth sorting methodology has changed between GM:S 1.4 and GMS2. In trying to find a solution that would work for my game i saw a lot of questions online without many answers. YYG has discouraged continuing to use the old "depth = -y;" method in GMS2 with the addition of layers. Posting this is my way of giving back to the community, to help newbies, since i received the help i needed from the community. I did get started with ideas and inspiration from the following sources...
After coding my own sorting method using the concepts discussed above i had an issue in that it only works for objects. Which is great for sorting characters, but what about landscape, Trees, rocks, buildings. From what i've seen many developers ended up making their "trees" into objects so it could be depth sorted by the same code.
I felt performance would take a hit after having too many objects. Since GMS2 has asset sprites and tilemaps for the exact purpose of reducing object over head, i wanted my trees (and other things) to be sprites instead of objects. I wanted a method that could depth sort objects and sprites.
Im also lazy and wanted a one and done solution, i made a controller object that once dropped on a layer, would handle all sorting without any setup work for the objects or sprites, no adding them to a list, no making them all children of a special parent object, no adding sorting code to their events, etc.
The process for the method i came up with is:
That is all there is to it. I will post the Controller Object code below. And discuss the performance i was able to get out of it. I only included Objects and Sprites in the sorting, however it can easily be modified to include tilemaps, backgrounds and particle systems.
I am also asking the community, if anyone can improve upon performance please post an improved version as it will help everyone.
Yes, it is true that using repeat loops are faster than for loops. And ds_list including sorting is faster than ds_grids, ds_maps and ds_priority queues which makes Ariak's binary list the best option IMO.
In my project i do not resize sprites using xscale or yscale so i did not add code to account for that as it would slow down the loops. If your project does change scale on sprites then you should add that in the Pre-Draw Event where it computes width and height (ie. width = sprite width * xscale). Or to speed the code up, hard code a fixed width and height number that would encompass the maximum size of your sprites. Then time wont be used computing it accurately for each sprite.
Now as for performance. I checked times using get_timer() which is in micro seconds. There are 1 million micro seconds in a full Second. When you check the value of delta_time in your project it will report how many micro seconds your CPU is doing per step of your game. On my PC i get around 16,000µs (micro seconds) per step running at 60FPS. Or around 8,000µs per step running at 120FPS.
Since i only sort objects and sprites that are within camera view, i only tested for the amount of objects/sprites in view and didn't concern myself with objects outside of the camera view. All though having thousands of objects/sprites outside of the camera view will add a tiny tiny bit more processing time as it still has to check if they are in view or not, but i felt it was insignificant enough to worry about.
I put 1,000 items on the layer within camera view, 500 objects and 500 sprites.
The sorting being done in Pre-Draw took on average 1,250µs.
The loop to draw those 1,000 items took on average 95µs.
This method used about 1,345µs to depth sort 1,000 items.
Which is 1.345ms (milliseconds) or about 8% of a step at 60FPS.
I also tested 2,000 items on the layer within camera view. 1,000 objects and 1,000 sprites.
Pre-sort took 4,200µs.
Draw loop took 200µs.
Thats 4,400µs || 4.4ms || 27% of a step at 60FPS.
My take away is for most average GMS2 games this method should work great. However if you have thousands of objects / sprites on screen then it could be considered slow. Even though i had a solid 120FPS with 2,000 objects, that's still 54% of the step time spent on just sorting. Doesn't leave a lot of time left for game logic code.
Id love to hear if anyone can improve on this.
I did like the basic concept of putting objects, that you want to have depth, into a sorted list and manually drawing them to the screen. I also found Ariak's suggestion of a binary list to be the fastest sorting method. That is when you bit shift two values (Instance Id, y) into a single number stored in a ds_list.FriendlyCosmonaut Youtube Tutorials:
YYG Dungeon Demo Included in GMS2
Z-Tilting Shader Depth Sorting by Ariak:
https://www.yoyogames.com/blog/458/z-tilting-shader-based-2-5d-depth-sorting
(Although this method isn't for me, someone else might find it useful.)
And this post by Ariak discussing the performance of different methods.
https://forum.yoyogames.com/index.p...rity-list-nested-list-grid-binary-list.13425/
I also asked for suggestions on discord /r/GameMaker
After coding my own sorting method using the concepts discussed above i had an issue in that it only works for objects. Which is great for sorting characters, but what about landscape, Trees, rocks, buildings. From what i've seen many developers ended up making their "trees" into objects so it could be depth sorted by the same code.
I felt performance would take a hit after having too many objects. Since GMS2 has asset sprites and tilemaps for the exact purpose of reducing object over head, i wanted my trees (and other things) to be sprites instead of objects. I wanted a method that could depth sort objects and sprites.
Im also lazy and wanted a one and done solution, i made a controller object that once dropped on a layer, would handle all sorting without any setup work for the objects or sprites, no adding them to a list, no making them all children of a special parent object, no adding sorting code to their events, etc.
The process for the method i came up with is:
- Make a layer that you will put all objects and sprites on that you want sorted. Mark this layer invisible. Making it invisible is to prevent their draw events from automatically running so the code can manually call their draw events in the sorted order.
- Add the Depth Controller object to a different layer, any layer. Bear in mind this layer is the depth all sorted objects and sprites will be drawn at, in relation to the other layers in your project. The only code change you need to do is define the name of the invisible layer in the Create Event of the Controller object.
- Now just add all objects and sprites you want sorted to the invisible layer, no need to do anything specifically to them.
That is all there is to it. I will post the Controller Object code below. And discuss the performance i was able to get out of it. I only included Objects and Sprites in the sorting, however it can easily be modified to include tilemaps, backgrounds and particle systems.
I am also asking the community, if anyone can improve upon performance please post an improved version as it will help everyone.
Yes, it is true that using repeat loops are faster than for loops. And ds_list including sorting is faster than ds_grids, ds_maps and ds_priority queues which makes Ariak's binary list the best option IMO.
In my project i do not resize sprites using xscale or yscale so i did not add code to account for that as it would slow down the loops. If your project does change scale on sprites then you should add that in the Pre-Draw Event where it computes width and height (ie. width = sprite width * xscale). Or to speed the code up, hard code a fixed width and height number that would encompass the maximum size of your sprites. Then time wont be used computing it accurately for each sprite.
Code:
/// @desc CREATE EVENT
// Create Instance Variables
depthList = ds_list_create();
sortingLayer = layer_get_id("put_invisible_layer_name_here");
Code:
/// @desc PRE-DRAW EVENT
// Clear Sort List
ds_list_clear(depthList);
// Define Camera View Coordinates
// ***Note: Change view_camera[0] if your project uses something different
var cam_x1 = camera_get_view_x(view_camera[0]);
var cam_y1 = camera_get_view_y(view_camera[0]);
var cam_x2 = cam_x1 + camera_get_view_width(view_camera[0]);
var cam_y2 = cam_y1 + camera_get_view_height(view_camera[0]);
// Define Local Variables
var loop = 0;
var xx = 0;
var yy = 0;
var width = 0;
var height = 0;
// Get All Elements On Invisible Layer And Loop
var elements = layer_get_all_elements(sortingLayer);
repeat (array_length_1d(elements)) {
xx = undefined;
// Object Instances
if (layer_get_element_type(elements[loop]) == layerelementtype_instance) {
var thisInst = layer_instance_get_instance(elements[loop]);
xx = thisInst.x;
yy = thisInst.y;
width = thisInst.sprite_width;
height = thisInst.sprite_height;
}
// Asset Layer Sprites
if (layer_get_element_type(elements[loop]) == layerelementtype_sprite) {
xx = layer_sprite_get_x(elements[loop]);
yy = layer_sprite_get_y(elements[loop]);
width = sprite_get_width(layer_sprite_get_sprite(elements[loop]));
height = sprite_get_height(layer_sprite_get_sprite(elements[loop]));
}
// If Within Screen View Add To Sorting List
if (xx != undefined && xx + width >= cam_x1 && xx - width <= cam_x2 && yy + height >= cam_y1 && yy - height <= cam_y2) {
ds_list_add(depthList, (elements[loop] | yy << 32) );
}
loop++;
}
// Sort List According to Y value
ds_list_sort(depthList, true);
Code:
/// @desc DRAW EVENT
// If you need to add this to BEGIN DRAW EVENT use event_perform(ev_draw, ev_draw_begin);
// If you need to add this to END DRAW EVENT use event_perform(ev_draw, ev_draw_end);
var loop = 0;
repeat (ds_list_size(depthList)) {
var element_id = depthList[| loop] &$ffffffff;
if (layer_get_element_type(element_id) == layerelementtype_instance) {
with (layer_instance_get_instance(element_id)) event_perform(ev_draw, 0);
}
if (layer_get_element_type(element_id) == layerelementtype_sprite) {
draw_sprite(layer_sprite_get_sprite(element_id), -1, layer_sprite_get_x(element_id), layer_sprite_get_y(element_id));
}
loop++;
}
Code:
/// @desc CLEAN UP EVENT
ds_list_destroy(depthList);
depthList = undefined;
Since i only sort objects and sprites that are within camera view, i only tested for the amount of objects/sprites in view and didn't concern myself with objects outside of the camera view. All though having thousands of objects/sprites outside of the camera view will add a tiny tiny bit more processing time as it still has to check if they are in view or not, but i felt it was insignificant enough to worry about.
I put 1,000 items on the layer within camera view, 500 objects and 500 sprites.
The sorting being done in Pre-Draw took on average 1,250µs.
The loop to draw those 1,000 items took on average 95µs.
This method used about 1,345µs to depth sort 1,000 items.
Which is 1.345ms (milliseconds) or about 8% of a step at 60FPS.
I also tested 2,000 items on the layer within camera view. 1,000 objects and 1,000 sprites.
Pre-sort took 4,200µs.
Draw loop took 200µs.
Thats 4,400µs || 4.4ms || 27% of a step at 60FPS.
My take away is for most average GMS2 games this method should work great. However if you have thousands of objects / sprites on screen then it could be considered slow. Even though i had a solid 120FPS with 2,000 objects, that's still 54% of the step time spent on just sorting. Doesn't leave a lot of time left for game logic code.
Id love to hear if anyone can improve on this.
Last edited by a moderator: