GMS 2 Binary List Depth Ordering Issue

Erayd

Member
I've been working on following this concept on binary lists as depth ordering: https://forum.yoyogames.com/index.p...rity-list-nested-list-grid-binary-list.13425/

I dived in to bitwise operators and read through several demo scripts by yoyo along with reading the guides on priority lists. At this point, I THINK I know what I'm doing, however the list sorting system just doesn't seem to work.

I have an object, obj_DrawController which I place in to a room on the "Instance" layer where all the other instances are. This object correctly grabs all the objects and stores them in this binary list to which I can then access and run code in. I've (in the draw event part) have tested putting output of the object index it finds and it does in fact store and then access each item.

The problem is they're all still just drawing in the same order I put them in the room. Nothing has changed, my player is still just on top of everything and anything I placed in the room renders based on where I placed it in the room, not it's y position. Could the draw event be running twice somehow? Could event_perform just not be working? Could the list idea be bugged and it doesn't work like this anymore? Maybe it just isn't sorting? I just don't know and any thoughts on this topic are greatly appreciated.

In case you're wondering, I do need event_perform because there is a lot going on in a single sprite that simply drawing its sprite at an x/y position isn't enough.

Create Event:
Code:
depth_table = ds_list_create();
Room Start Event
Code:
/// @description Fill Depth Table

//Clear the list
ds_list_clear(depth_table);

//Add all instances to depth table except this one
var _id = 0;
for(var i = 0; i < instance_count - 1; i++){
    _id = instance_id_get(i);
    if(_id != id) depth_table[| i] = (_id | _id.y << 32);
}

//Sorth the depth list
ds_list_sort(depth_table, false);
Draw Event:
Code:
/// @description Draw all objects
var _id = 0;
for(var i = 0; i < ds_list_size(depth_table); i++){
    _id = depth_table[| i] & $ffffffff;
    with(_id) {
        event_perform(ev_draw, 0);
    }
}
 
Have you made the objects that the draw controller is drawing invisible? You should be doing this. If they're not invisible, you'll have the problem of the draw controller drawing them in the correct order, and then them drawing themselves a second time in the order that you placed them in the room, negating the effect of the draw controller.
 

Erayd

Member
You are exactly right @RefresherTowel Do you know how much time I've spent trying to figure out if the objects draw event would also run if I forced the draw in the draw controller as well? I couldn't find out how to disable it either, only method I found was the draw_enable_drawevent(false) method which super messed things up. I mean yes it's super simple to just hit the checkbox for visible to off but theres so many moving parts, I haven't looked at that checkbox in AT LEAST a year, I forgot it even existed. I've still got a lot of work updating objects with bad y origins and shaders I had on certain layers which are now out of whack but you've helped me break through that final wall on this crazy topic. Thank you very much.
 

Hyomoto

Member
The visible checkbox should not be understated for it's value, and should be unchecked for everything that doesn't draw something to the screen. There are five primary draw events, and three GUI draw events. When the visible box is not checked, no draw-related events are run for that object. Even an object with no sprite has a in-built draw_self() event during the draw event, so it's a helpful optimization to discard everything from the draw pipeline that doesn't need to be there. However, I think you are misunderstanding the way these depth ordered systems work, to some degree. Consider the following, I have two objects, objectA and objectB. In the draw event of objectA I do this:
Code:
draw_sprite( objectB.sprite_index, objectB.image_index, objectB.x, objectB.y );
In this case, objectA is drawing objectB's sprite, but objectA is, for all intents, doing the drawing. Now, let's look at a modified version:
Code:
with ( objectB ) { draw_sprite( sprite_index, image_index, x, y ) }
In this case, objectB is now drawing itself, but using code is located in objectA's draw event. This is an important distinction, because the code belongs to objectA, but it's being performed as objectB. Most importantly, it does not supersede objectB's draw event. ObjectB is unchanged, and is still queued up to perform its event as normal. Let's modify this even further:
Code:
with ( objectB ) { event_perform( ev_draw, 0 ) }
At this point objectA is quite literally making objectB perform it's own draw event. From an internal perspective, GML believes two things: that objectB is the current scope, and that its draw event is the current event. However, this has nothing to do with whether or not GM has objectB as part of the list of objects to call during the draw step. And again, it does not supersede objectB's draw event. objectB is still unchanged, and is still going to perform its event as normal. This is why you have to uncheck the 'visible' box. If you are going to have another object call the draw event for objectB, then you must tell GM "do not call this object's draw events during the draw phase." In your example, using draw_enable_drawevent( false ) literally just disables the draw event altogether. From the manual,
With this function you can choose to enable (true) or disable (false) the draw event for all instances in the game, thus giving you control over how and when things are draw, useful if you wish to implement a "frame skip" technique. Note that this doesn't just prevent instances drawing to the screen, it suppresses the draw event completely meaning that care should be taken since any game logic that is present in that event will not be run either.
There's something else you've said that perks up my ears, "theres so many moving parts". You've said that disabling the draw event "super messed things up". Well, check the above passage, "this doesn't just prevent instances drawing to the screen, it suppresses the draw event completely meaning that care should be taken since any game logic that is present in that event will not be run either." This is why game logic should never be placed in a draw event, you are tying the rendering of your game to the processing of game logic. That is bad. There is a reason you have both a step event, for logic, and a draw event, for rendering. If you have had issues with turning off the draw event for an object in the past, it most likely means that you have code in your draw event that shouldn't be there: the only result of making an object not visible should be that it is no longer visible. And, in the case of depth-sorting, you are doing so because you intend to handle the drawing yourself.
 
Last edited:

Erayd

Member
By super messed things up I mean I have more than just objects in my game. There are tiles for example, on different layers, which are hooked to certain layer shaders for things like showing the player through an alpha adjusted circle of an overlapping house as the player walks behind it. I figured every draw event would be off by using that function, so I totally expected nothing to draw since I don't have a controller drawing anything to the game outside of the usual draw loop.

Once I knew to set the instances I care about depth tracking to invisible, all I had to do was open the game, uncheck that box and everything worked perfectly. I always keep 99.9% of logic outside of the draw event. Sometimes for things like message boxes and certain inventories, that can be more complicated than it's worth, hence the 0.01% of logic showing up in some draw events. However none of those objects are things I need to depth sort, all I care about are the player, other NPCs, trees, items etc.

I understand the differences in simply calling draw_self() vs calling a whole draw event, hence why I was specifically trying to get this way to work. I don't want to have every object in my game's draw code to be in this one draw controller. Not only is that messy but it's confusing. Calling the draw event instead leaves all objects up for custom coding as my game moves forward, without having to remember the depth system is even plugging along in the background. Sometimes, that draw event may just contain a draw_self() which isn't so optimized, but most of the time it contains way more than just one thing being draw at once. For example, I plan on having several different pieces of equippable clothing for the player that actually show on the players sprite as they walk around. Haven't done the system yet, so I could be wrong, however my initial plan is to have each piece of clothing being picked from its saved location and drawn for the players body for each frame in the players draw event. I'm not putting that code in a totally unrelated object.

When I say "So many moving parts" I mean that in the broadest sense. I don't mean just the draw event and I don't mean if I change one tiny thing everything breaks. If I'm working with the entirety of how the game draws, things are going to change big time since it's a foundation piece that everything stands on. When it comes to drawing there are layers which objects can be created on, special managed layers which disappear if no instances are on them (which supports the old way of using depth which my game originated on), theres the draw event and knowing when that runs relative to the instance in the instance list, the visibility checkbox, the functions that go along with all of these different pieces and none of these draw related parts are what really makes this whole system work. That being the binary list, which I have been deep diving in to lately. When I say "lot of moving parts" I mean I've been reading a lot of documentation on a lot of different functions and I missed one checkbox along the way.
 
Top