Depth Sorting for GMS2 (Alternative to "depth = -y")

Discussion in 'Tutorials' started by FriendlyCosmonaut, May 6, 2017.

  1. FriendlyCosmonaut

    FriendlyCosmonaut Member

    Joined:
    May 6, 2017
    Posts:
    3
    GM Version: GameMaker Studio 2
    Download: Example File
    Links: Asset on Marketplace

    Summary:

    In GMS1, "depth = -y" served as a simple, fast, dependable way to sort depth dynamically, especially for beginners. In GMS2, with the introduction of layers, it's easy to run into some trouble with this method. Therefore, I've tried to come up with a relatively simple method that I hope won't be too tricky for beginners.

    My system doesn't actually rely on "depth" at all. Instead, we take advantage of the draw order of instances. We require two objects and one script: (1) a master draw object, (2) a parent object whose children will be every object whose depth you want to sort. The script adds these objects to the system. This is the gist of it:
    • We set up a ds_grid with two columns: one for instances' IDs, and the other for instances' Y values.

    • All the object instances add themselves and their Y position to this grid via a script. We also disable instances' default draw event so that they are no longer drawing themselves.

    • A master "draw" object then sorts the grid by the "Y value" column, so that instances with a lower Y (which are up the top of the room) come first in the grid.

    • The master draw object then loops through the grid and draws all the objects in order. That way, instances with lower Y values are drawn first, behind objects that have higher Y values.

    Tutorial:
     
    houdinik, Andrey, Ariak and 1 other person like this.
  2. MirthCastle

    MirthCastle Member

    Joined:
    Mar 26, 2017
    Posts:
    147
    This is a great idea. Do you think you could have done it with a priority queue (Q)? Then you wouldn't have to sort and resize the grid every time. The Q would be the Q ?
     
  3. FriendlyCosmonaut

    FriendlyCosmonaut Member

    Joined:
    May 6, 2017
    Posts:
    3
    Thank you!

    I've actually had some great feedback about this recently. You certainly could do it with a priority queue, but (in GM at least) they are slow to read from. As data structures go, grids are much faster. However, resizing the grid is a performance sink indeed! And actually, if the system is set out a little differently, it's not necessary to be done much at all.

    I'm working on a follow-up video on how to optimize the system - all thanks to @Ariak, who has given me quite a few suggestions/improvements!
     
    atmobeat likes this.
  4. Tempus

    Tempus Member

    Joined:
    Nov 27, 2016
    Posts:
    42
    Very nice tutorial. Thanks.

    If i get you right. You put the drawself() of an object in a function with a depth sorting.
    Is it possible to get a sprite onto the depth grid?
    For example i have an object with the sprite of a body. Now i want to put some armour on it. Like a helmet or a shoulderplate. Or i want a separate arm which is aiming at the mousepointer.
    I could do it with separate objects but i would prefer simple sprites.

    Like
    Draw event:
    Draw sprite 1 (arm in the background)
    Drawself (body)
    Draw sprite 2 (arm in the foreground)
    Draw sprite 3 (shoulderplate over the arm in the foreground)
     
  5. FriendlyCosmonaut

    FriendlyCosmonaut Member

    Joined:
    May 6, 2017
    Posts:
    3
    Thank you @Tempus! And yes, this would be possible. In the draw event of obj_draw, you could do a check before "draw_self()" for some kind of variable. For example, you could add a variable to the parent object (obj_parent_depth) of all the instances. In its create event, add:
    Code:
    drawAlternate = false;
    
    This will be the "default setting". Now, in the obj_player (or whatever you've called it), we will change this value to be equal to an alternate draw script we will perform. So go ahead and make a script, you can call it whatever you like. For example, "player_drawAlternate". Then in that script, we can add whatever code you want that object instance to draw. For example:
    Code:
    draw_self();
    draw_sprite(spr_helmet, image_index, x,y);
    draw_sprite(spr_body, image_index, x,y);
    draw_sprite(spr_legs, image_index, x,y);
    
    Back in obj_player's create event, set "drawAlternate = player_drawAlternate", or whatever you called the script.

    Then, in obj_draw's "Draw" event, we change the "with(instanceID){ draw_self() }" bit to:
    Code:
    with(instanceID){
        if(drawAlternate == false) draw_self();
        else { script_execute(drawAlternate) }
    }
    
     
  6. JAG

    JAG Member

    Joined:
    Jul 5, 2017
    Posts:
    70
    @FriendlyCosmonaut hiya great video! Do you know of some way to do something similar with tiles? I have a tileset with 2 tiles, where one is the base of a pillar, and the other is the top. The player collides with the base but not the top. The issue is that the player walks over the top of the pillar instead of behind. Not sure how to deal with it?! I dont want to create a new object for every "pillar top" and set the depth, that seems ridiculous. But maybe in GMS2 tiles are always supposed to be below objects?

    Thanks!
     
  7. csanyk

    csanyk Member

    Joined:
    Jun 20, 2016
    Posts:
    815
    Just a thought, but do you really need to draw EVERY instance in its draw order?

    It seems to me, you only need to worry about draw order when instances are overlapping one another.

    Sorting every single instance every step just so you can draw them all in order seems like a lot of overhead, and not really necessary.

    I'd suggest as an alternative, try adding only instances that are overlapping (eg in collision) to a data structure, then sort those instances, and draw them in the order they need to be drawn in. All other instances can be drawn in arbitrary order, and it shouldn't matter.

    This may help if you have a room with a lot of stuff in it, but not too much stuff that's overlapping.

    For background stuff that doesn't need to be an object, you can put them into layers, and then move instances between layers when they are higher/behind the background objects in a given layer. Certain layers (such as the ground/floor) can be left way in the background, and other layers can be in front of each other.

    It would also potentially be a good idea to weed out any instances that are completely occluded from being drawn at all, and save some time that way.
     
  8. atmobeat

    atmobeat Member

    Joined:
    Feb 24, 2017
    Posts:
    17
    I'm not sure that this is a lot of overhead since we are just sorting a bunch of numbers in a single grid and pretty much all of them need to be drawn anyway so that doesn't add anything, but I like the idea of trying to make this strategy more optimized. Unfortunately your proposed solution won't work. Many objects like buildings, trees, etc. should sometimes be drawn over a character even though the character isn't colliding with the object. Collision masks don't always match up with the corresponding sprite 1 to 1.

    I agree that there seems to be room for optimization here, though. It seems that once the grid is filled and sorted, you should only have to sort it when something's y-position changes or when something new is created or when something is destroyed (after being added or deleted of course). You probably don't have to do anything when something is destroyed but maybe there are some corner cases I'm not thinking about.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice