• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Legacy GM Gamemaker's default code for drawing tiles?

C

Corablue

Guest
For collision in my previous projects I used the old method of having a reference object (obj_wall or something similar) and checking collision against that instance. There are a lot of logistical problems with this approach in more complex rooms, the least of which being GM's editor, so I'm embedding collision into the tiles themselves.



The idea is to draw only the tile layer used for collision (layer -9999) to a surface, and then transform that surface into a sprite used for collision checks. There's a very simple bit of code to do this in theory, and here's what I have so far.

Code:
//Hide everything except collision
var num = tile_get_count();
for (var i = 0; i < num; i++;)
    {
   tile_set_visible(tile_get_id(i), false);
    }
   tile_layer_show(-9999);

//Create surface for collision map
col_surf = surface_create(room_width, room_height);
surface_set_target(col_surf);
//DRAW HERE
surface_reset_target();
spr_col_map = sprite_create_from_surface(col_surf, 0, 0, room_width, room_height, true, false, 0, 0);

//Show everything except collision
var num = tile_get_count();
for (var i = 0; i < num; i++;)
    {
   tile_set_visible(tile_get_id(i), true);
    }
    tile_layer_hide(-9999);
//DRAW HERE is where I'm stumped.

The layer show and hide code works fine, though if there's a more efficient way to do it than looping every single tile I'd like to know that too. What I'd really like to know, however, is the code GM uses by default to draw tiles to the screen. After I set the surface to col_surf, what am I telling GM to draw? How do I get just this drawn to the surface?



Any help is hugely appreciated. Thanks!
 
A

anomalous

Guest
You can use draw_background_part() to draw a tile yourself (and the related commands).
If your tiles are fixed per level, you can do all your calculations and either save that to a file you read in at the start of a level. That can be saved as the actual png, a grid with the data, etc. Well, that's only if you are wanting to speed things up I suppose you didn't really ask about that.

As for tile operations, if you are lucky enough to be using specific depths, there are a number of commands that affect the entire depth (delete, hide, move to new depth, etc.) you can check out in the manual, I think last I ran them they were much faster than doing it by tile.
 
C

Corablue

Guest
You can use draw_background_part() to draw a tile yourself (and the related commands).
If your tiles are fixed per level, you can do all your calculations and either save that to a file you read in at the start of a level. That can be saved as the actual png, a grid with the data, etc. Well, that's only if you are wanting to speed things up I suppose you didn't really ask about that.

As for tile operations, if you are lucky enough to be using specific depths, there are a number of commands that affect the entire depth (delete, hide, move to new depth, etc.) you can check out in the manual, I think last I ran them they were much faster than doing it by tile.
Okay, using draw_background_part() would work, but I want to run it passed someone to make sure I'm understanding you correctly. Right now I have this.

Code:
    //DRAW HERE!!
    var num = tile_get_count();
    for (var i = 0; i < num; i++;)
        {
        tile = tile_get_id(i);
        if tile_get_visible(tile) then draw_background_part(tile_get_background(tile), tile_get_left(tile), tile_get_top(tile), tile_get_width(tile), tile_get_height(tile), tile_get_x(tile), tile_get_y(tile));
        }
If this is the most efficient way to draw all visible tiles to the screen, then great because that makes some of my other code redundant. However I'm not sure this is the way GM does it by default. Does this look right?
 
S

SyntaxError

Guest
GM just draws all visible tiles that have been added to the room. You could however change your code a little.

Code:
 //DRAW HERE!!
tile_array = tile_get_ids_at_depth(-9999);
for (var i = 0; i < array_length_1d(tile_array); i++;)
    {
    var tile = tile_array[i];
    draw_background_part(tile_get_background(tile), tile_get_left(tile), tile_get_top(tile), tile_get_width(tile), tile_get_height(tile), tile_get_x(tile), tile_get_y(tile));
    }
 

Yal

🐧 *penguin noises*
GMC Elder
When you say 'visible' tiles, do you mean "all tiles partially inside the view"? If you plan to update the surface every step and only capture the tiles in the view on it (which is a sound approach given that surfaces are volatile and eat up a lot of RAM), I'd say it's more efficient to do a nested loop over the view, stepping with the tile size, checking for tiles and for every you find, draw them to [tile's position] - [view position] on the surface. For small views/large rooms, this should do a pretty big difference, at least on VM targets.
 
C

Corablue

Guest
GM just draws all visible tiles that have been added to the room. You could however change your code a little.

Code:
 //DRAW HERE!!
tile_array = tile_get_ids_at_depth(-9999);
for (var i = 0; i < array_length_1d(tile_array); i++;)
    {
    var tile = tile_array[i];
    draw_background_part(tile_get_background(tile), tile_get_left(tile), tile_get_top(tile), tile_get_width(tile), tile_get_height(tile), tile_get_x(tile), tile_get_y(tile));
    }
Thanks. That is more efficient. I'll use that for sure.

When you say 'visible' tiles, do you mean "all tiles partially inside the view"? If you plan to update the surface every step and only capture the tiles in the view on it (which is a sound approach given that surfaces are volatile and eat up a lot of RAM), I'd say it's more efficient to do a nested loop over the view, stepping with the tile size, checking for tiles and for every you find, draw them to [tile's position] - [view position] on the surface. For small views/large rooms, this should do a pretty big difference, at least on VM targets.
I hadn't thought about RAM usage. I'll consider this and perhaps rewrite what little I have as you suggest. Probably not good for RAM to be storing a 12000 pixel wide texture for large rooms. Unfortunately running it per step makes it a little more complicated than I originally estimated, but c'est la vie.
 

Yal

🐧 *penguin noises*
GMC Elder
At the very least, you should be prepared that surfaces may disappear at any time, especially on mobile devices. (I personally don't trust them and never use them, but it's probably too cautious.) On desktop targets, things like fullscreening, the screen saver triggering, or alt-tabbing will break surfaces, and while it's not as ubiquitous as on mobile devices that can get phonecalls at any time and that often are multi-tasked by their users, it would still ruin the game experience if it happened partway through a level and you only created the surface at the start of the room and never touched it again, resulting in a suddenly corrupted level.

Actually, an approach I've investigated lately is to just check for the tiles themselves - if you just need box collision, this is a VERY fast approach. Have one layer for 'solid' tiles and put decoration tiles in other layer(s); then simply do collision checks by checking if a point has a tile in the collision layer. You can expand it further by looking up the tile's left/top and having certain positions in the tileset be slopes and such, then do the matching collision check; it's safer than surfaces and faster as well compared to pixel-precise collision checks. More complicated, but that's life.
 
C

Corablue

Guest
It works great! Here's a video of it in action. There is only 1 object controlling all the collisions for the entire room, and a single texture. All in all, this code uses almost no additional CPU usage over the traditional method, and 4 MB of RAM maximum.


There is one small issue (it might not be an issue, I just want some feedback). You know how there's a 'memory leak' in GM when a player jumps off the screen because the y value is increasing wildly and the variable needs more and more bits to hold the data? That's happening here on a smaller scale. Every few frames the surface takes up another bit because this number is increasing.



Its not a huge deal because by the 100,000th frame it'll basically never change again and we're talking kilobytes maximum, but this doesn't seem like good practice. Is there a way to assign a static index for this stuff? Here's the full code.

STEP
Code:
sprite_index = sprite5;
x = view_xview[0];
y = view_yview[0];

//Create surface for collision map
col_surf = surface_create(view_wview[0], view_hview[0]);

//Set the surface
surface_set_target(col_surf);

//DRAW HERE!!
draw_clear_alpha(c_white, 0);
tile_array = tile_get_ids_at_depth(-9999);
for (var i = 0; i < array_length_1d(tile_array); i++;)
    {
    var tile = tile_array[i];
    draw_background_part(tile_get_background(tile), tile_get_left(tile), tile_get_top(tile), tile_get_width(tile), tile_get_height(tile), tile_get_x(tile) - view_xview[0], tile_get_y(tile) - view_yview[0]);
    tile_set_visible(tile, false);
    }

//Reset the surface
surface_reset_target();

//Free the sprite
if spr_col_map != noone sprite_delete(spr_col_map);

//Create sprite using the generated collision mask
spr_col_map = sprite_create_from_surface(col_surf, 0, 0, view_wview[0], view_hview[0], false, false, 0, 0);
sprite_collision_mask(spr_col_map, false, 0, 0, 0, sprite_width, sprite_height, 0, 0);

//Free the surface
surface_free(col_surf);

//Create the collision object in the scene
sprite_index = spr_col_map;
 
S

SyntaxError

Guest
Seems you are making/freeing the surface every step. Is that necessary? I haven't worked with surfaces at all but if I get the manual, you could encompass all the above code in...
Code:
if (!surface_exists(col_surf)
{
    // Above code block.
}
That would make it faster and also slow dramatically the upwards counting surface and sprite id's.

I'm not sure how alt tabbing would effect surfaces and weather or not they still exist but are maybe garbled, but you could also maybe add some check using window_has_focus.

Also, I also think the id number rising has no effect as it has 32 bits assigned to the memory allocation. When it gets to 2bil, it resets to 0. I'm probably wrong on that, I don't know the inner working of GM.

I asked a similar question regarding image_index a while ago and the response was similar.
 
C

Corablue

Guest
Seems you are making/freeing the surface every step. Is that necessary? I haven't worked with surfaces at all but if I get the manual, you could encompass all the above code in...
Code:
if (!surface_exists(col_surf)
{
    // Above code block.
}
That would make it faster and also slow dramatically the upwards counting surface and sprite id's.

I'm not sure how alt tabbing would effect surfaces and weather or not they still exist but are maybe garbled, but you could also maybe add some check using window_has_focus.

Also, I also think the id number rising has no effect as it has 32 bits assigned to the memory allocation. When it gets to 2bil, it resets to 0. I'm probably wrong on that, I don't know the inner working of GM.

I asked a similar question regarding image_index a while ago and the response was similar.
I need to refresh the surface every step, because as the camera moves, the collision objects on screen change. The best I could do would be something like...

Code:
if (obj_player.x != obj_player.xprevious or obj_player.y != obj_player.yprevious){
     //Refresh surface
}
Unless I'm missing something?
 

TheouAegis

Member
Or you scroll the surface counter to the view and every tile_width/walking_speed steps you refresh the surface.

Or you write a collision map, which isn't volatile like surfaces, and just code your engine around that.
 
C

Corablue

Guest
That's actually a really good idea TheouAegis. I think the wise thing to do might be to overscan and only update the collision map every 4-5 tiles traveled.
 
Top