• 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!

GMS 1.4 - Some questions for experienced GML programmers (Platformers)

Wile94

Member
Hello, first of all I'm not an english native speaker so I apologize in advance if something doesn't make sense.

I've been learning GML for about a year and a half on my own by watching tutorials and reading forums. I originally started to "code" on M.U.G.E.N. (fighting game engine by elecbyte, some of you may know it), so moving to GML seemed to be pretty challenging but I kinda get my head around how it works. Instead of changing variables in states controllers (mugen language), I got to make my own "engine" from scratch which it was hard as hell at the beginning. I've played videogames my whole life and I was very interested in programming when I was a kid, but I ended up doing it just as a hobby and I pursued music composition/production as my actual career.
I made some rpgs and small platformer games to keep myself learning and understanding how GameMaker handles memory, graphics, views, bounding boxes, collisions, animations, data structures, etc. I learned how to replace "image_speed" with ds_lists or arrays to handle my animations, replace all the "solid objects" with a ds_grid based collision system, animate tiles and bgs, export my levels into "*.ini" files and so on.

Lately I'm being pretty much concerned about how to manage performance in GM because I'm making a pretty big fighting/action/roguelike platformer pixel art project:

  • I made a random map generator so, when a new run starts, it makes a complete (sort of hehe) new level with severals rooms kinda like Binding of Isaac. Each room holds it's own amount of randomness (enemies, pickups, moving platforms, items, traps, secrets, etc.).
  • Terrain is made with a ds_grid which I use to handle collisions and place my tiles. Cells are 16x16 pixels.
  • The game runs in only one room (main menu and stuff got their own). When the player leaves a level section, I save evey instance left behind in a ds_map/ini file, restart the room and load the new section.
  • Resolution is 640x360. I split the whole room size with this params (more or less) and create a grid to handle activation/deactivation. I check when the view corners enters an unactive cell and run deactivation/activation region function so it doesn't do it every step. Some instance deactivate themselves when leaving active cells.
  • I have only one persistent object that handles key config, graphics config, score, unlocks, save files, etc. Then when a run starts I have some controllers: one for map generation (creates the map, save up ini files to load when needed and keeps track of triggers like door switchs or portals), one for level section (holds the terrain grid, place it tiles and instances), one for graphs and fx (animated tiles/bgs, lighting surfaces) and one for the view (follows the player and handles the activation/deactivation of instances). Only this last two have step events or at least the more coded ones.
  • Room speed at 60fps.

I'm still developing my first level, and my frame rate is quite high (600-700 average). Although, sometimes the game gets a little laggy, mostly (not always) when I enter a big section of a level (1920x1344 is the largest room so far and I looking to make some larger ones) dropping to 300-180 and sometimes below 60 :( . I think it's mainly because of my laptop (Intel HD 4000, 8gb RAM, i7-3632QM @2.20ghz-2.20ghz) but it should handle well this type of retro-pixel art games.
Here are some of my doubts:

  1. If tiles aren't inside the view, they won't be drawn. Is this correct?
  2. If that's not the case, What if I try splitting my tiles into several layers so I can use tile_layer_show/hide when they're inside/outside the view? Will this boost my fps? Will it drop them? Or will it make no difference at all? The fact of having so many layers makes me think it may be counterproductive.
  3. I tried to draw my tiles into a surface, this kinda boosted my fps in smaller rooms and it didn't make much of a difference in bigger ones. Then I read that creating a surface will make a new texture sheet, should I avoid this? My first level is a cave and using surfaces instead of tiles grants me the option of swap the color palette and mix things up a little more, because... you know... more random stuff :p.
  4. The player has plenty of mechanics and abilities: walk, run, jump, wall jump, ledge grab, combo attacks on ground and air, throw bombs/stuff, shoot bullets, getting hitted in various ways, swim. I use state machines for this but I read somewhere that using multiple objects instead of one may improve performace. What do you think about this?
  5. Enemies should use their own draw events? Or is it better to use one object to draw them all? Same question with pickups (coins, health boost, etc). I mean, I use one object to draw several lighting objects in a surface...
  6. Is it better to have a room sized static surface? Or a view sized dynamic surface? This last one should take less GPU, right?

I know, I know... I read many times to not worry about performance issues until there's a real one. But as I said, I'm a hobby programmer, I don't do this for a living, I'm just doing for the love of learning and making art. This questions may even help beginner programmers like me who are looking to make a living of this.

Cheers

I'm using Windows Target Platform by he way...
 
Last edited:

Simon Gust

Member
1. Yes this is correct.
2. Therefore not worth it.
3. Tiles are heavily optimized, so using a surface can end up not being worth it that much. The room size may as well be the problem in this. You only have so much VRAM, and surfaces take a lot. What you can do that is much more VRAM, CPU and GPU friendly is using vertex buffers. Instead of using width * height * 4 bytes of VRAM, it directly depends on how many tiles you have in your room. Vertex buffers can be frozen so that they turn read-only and are even faster to submit.
4. First, it's a hassle. Second, it eats up resources. Third, this really isn't an area you should try to optimize. You should keep the state machine.
5. One object should draw EVERYTHING. This is because then, you have full control in what order things are drawn and make sure to not break batches / swap textures between draw calls, which can slow down your game considerably.
6. You're trading GPU usage with VRAM usage. With static surfaces, you can draw everything on it once and then only draw the surface. With view sized surfaces however, you're always clearing it and redrawing everything in the view. If you're room is small enough a static surface is better. If your room is too large, you cannot allocate enough memory nor have the texture page size to support it. Like I said before, if everything is static and you do not have animations in you tiles, you should use a vertex buffer.

You should also take a look on how you're handle player to enemy / projectile collision, as collisions in general take a lot of CPU.

To better understand what is eating your performance, use the debugger.
Or call show_debug_mode(1);
 

Relic

Member
Your written English is amazing. I’d say better than mine and I don’t get any excuses about knowing two languages.
 

Wile94

Member
What you can do that is much more VRAM, CPU and GPU friendly is using vertex buffers
I've seen this function before but never really used it, I've only used normal buffers to store surfaces so I don't have to use the draw_getpixel function for some .png to ds_grid tests. I'll look into it!

You should also take a look on how you're handle player to enemy / projectile collision, as collisions in general take a lot of CPU.
From the player object, the only collision it gets from an enemy is when there's one near it's position and if that enemy has a "hurt on touch" flag. Only then I call the collision checking functions. Then with melee attacks and projectiles I use only one obj_hitbox which holds damage and hit type values and only the player and/or enemy checks a collision with that object if near one and if obj_hitbox.target matches it's object index... Am I doing this correctly?

To better understand what is eating your performance, use the debugger. Or call show_debug_mode(1)
Only recently I've been using these because I didn't really understand how to use them before, show_debug_mode really helped me figure out that my drawing events were the ones eating up my memory mostly, that's why my questions were more graphics related. I'll keep digging and learning, thanks for the response!

Your written English is amazing
Hehe, thanks!
 

Simon Gust

Member
From the player object, the only collision it gets from an enemy is when there's one near it's position and if that enemy has a "hurt on touch" flag. Only then I call the collision checking functions. Then with melee attacks and projectiles I use only one obj_hitbox which holds damage and hit type values and only the player and/or enemy checks a collision with that object if near one and if obj_hitbox.target matches it's object index... Am I doing this correctly?
Sounds good, but the "only check collision when near the player" can go wrong if you're not careful and actually increase strain on the cpu.
You may have to show us how you've implemented it.

Only recently I've been using these because I didn't really understand how to use them before, show_debug_mode really helped me figure out that my drawing events were the ones eating up my memory mostly, that's why my questions were more graphics related. I'll keep digging and learning, thanks for the response!
The numbers that are displayed by show_debug_mode are very important, you have to keep them as low as possible.
They signal batch breaks and texture swaps.
A batch breakes when you change a draw setting in the middle of drawing stuff or when you submit a surface or vertex buffer, set a shader or start drawing primitives in general.
A texture swap is caused by drawing sprites that are on different texture pages as only 1 can be loaded at a time. Make sure to optimize your texture pages and your draw order.
 

Wile94

Member
Sounds good, but the "only check collision when near the player" can go wrong if you're not careful and actually increase strain on the cpu.
You may have to show us how you've implemented it.
I change my code like all the time because I always find a new way to do it. Here's the last one i've made. This goes in the obj_player step event after all physics coding are runned:

Code:
//Check for enemies in the room
var theresEnemy = instance_exists(obj_enemy);
if (theresEnemy)
{
    //find nearest
    var nearEnemy = instance_nearest(x, y, obj_enemy);
    if (nearEnemy != noone)
    {
        //check for collision distance
        var _dist = point_distance(x, y, nearEnemy.x,nearEnemy.y);
        var onDist = (_dist <= minEneDist);
        if (onDist and !hitted) if place_meeting(x,y,obj_enemy)
        {
            var _enemy = instance_place(x,y,obj_enemy);
            var getHurted = (_enemy.hitOnTouch);
            if (getHurted)
            {
                hitID = _enemy;
                hitted = true;
            }
        }
    }
}
Then on the end step event I process all the hitted state variables. I use a pretty similar code with the obj_hitbox but instead of checking "hitOnTouch" I check the hitbox.target variable and then check for collisions.

A batch breakes when you change a draw setting in the middle of drawing stuff
Yeah, I use them pretty much for debugging mainly drawing text and yeah drawing text takes a lot of memory, right? Anyway I've always set a key to show/hide my debugging draws so I only see them when I need them
 

Simon Gust

Member
I change my code like all the time because I always find a new way to do it. Here's the last one i've made. This goes in the obj_player step event after all physics coding are runned:

Code:
//Check for enemies in the room
var theresEnemy = instance_exists(obj_enemy);
if (theresEnemy)
{
    //find nearest
    var nearEnemy = instance_nearest(x, y, obj_enemy);
    if (nearEnemy != noone)
    {
        //check for collision distance
        var _dist = point_distance(x, y, nearEnemy.x,nearEnemy.y);
        var onDist = (_dist <= minEneDist);
        if (onDist and !hitted) if place_meeting(x,y,obj_enemy)
        {
            var _enemy = instance_place(x,y,obj_enemy);
            var getHurted = (_enemy.hitOnTouch);
            if (getHurted)
            {
                hitID = _enemy;
                hitted = true;
            }
        }
    }
}
Then on the end step event I process all the hitted state variables. I use a pretty similar code with the obj_hitbox but instead of checking "hitOnTouch" I check the hitbox.target variable and then check for collisions.
hmm, looks like it could be a problem with many enemies.
You could try this:
Code:
var xx = x;
var yy = y;
var dis = 15; // player hitbox radius
with (obj_enemy) // initiates the loops through every enemy (also works as instance_eixsts)
{
   if (!hitOnTouch) continue; // enemy doesn't deal contact damage -> skip
   if (point_distance(xx, yy, x, y) < dis) // is the enemy in contact range?
   {           
       other.hitID = id;
       other.hitted = true;
       break; // player is hit, break the loop
   }
}
 

Wile94

Member
You could try this:
Code:
var xx = x;
var yy = y;
var dis = 15; // player hitbox radius
with (obj_enemy) // initiates the loops through every enemy (also works as instance_eixsts)
{
   if (!hitOnTouch) continue; // enemy doesn't deal contact damage -> skip
   if (point_distance(xx, yy, x, y) < dis) // is the enemy in contact range?
   {          
       other.hitID = id;
       other.hitted = true;
       break; // player is hit, break the loop
   }
}
Holy hell, that's so much simpler. It makes me realize I've might been using the with() loop the wrong way all along. Thanks a lot!
 
Top