Legacy GM Game gets very laggy when there are a lot of enemies

E

EndermanTamer13

Guest
I have a problem with this game I am working on, where in the harder stages of the game, when there are a lot of enemies in the room, the game lags a lot. It may be because I have all my rooms set to 120fps, but I doubt that would be it. I don't use the draw event when objects are outside the view, and the game doesn't spawn more enemies if there are more than 100, so I'm not really sure what's going on. I'm wondering if it might be the room size (3072x1728), but I also doubt that. The window size is 1536x864; not sure if that's the issue? I really have no idea why my game lags so much when there are many enemies in the room, so any assistance would be appreciated. I can upload the project file if that would help.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Pfff.... Okay, first, while 120fps is nice 60fps is perfectly fine and anything higher is probably just overhead, so I'd definitely change that. Second, are you displaying the whole room or just a view? If you are fisplaying the whole room then keep in mind that the average monitor size is 1920x1080 so anything higher is just wasting GPU time. Also, you don't need to check for things to be in view to draw them yourself, as this will be culled/handled by the GPU anyway... finally, run the game with the debugger and the profiler and see where the issue is. ;)

Note that in my game Skein, I can have over 3000 enemies active before the FPS dips below 100 (and it's got lighting, particles, AI, etc...) so GMS is more than capable of dealing with a few hundred. However you WILL have to program them correctly and efficiently.
 
E

EndermanTamer13

Guest
Pfff.... Okay, first, while 120fps is nice 60fps is perfectly fine and anything higher is probably just overhead, so I'd definitely change that.
Well, my second monitor runs at 120hz, and I usually play games on that monitor, so I'd like to have the game I make run as smooth as possible.

Second, are you displaying the whole room or just a view? If you are fisplaying the whole room then keep in mind that the average monitor size is 1920x1080 so anything higher is just wasting GPU time.
No, I'm not displaying the whole room. I might have scrambled what I said a little, but the room is 3072x1728, and the view, as well as the window size, is 1536x864.

finally, run the game with the debugger and the profiler and see where the issue is. ;)
I'll try that. Not sure what to look for in the debugger because I've never really had to use it like this, though.
 
E

EndermanTamer13

Guest
Alright, so I managed to change a few variables in the game so I could quickly get to the particularly difficult parts where it lags quite a lot. Here is a screenshot of the Profiler:



It looks like place_meeting and obj_chase.Step are used the most. What could this mean? Here is the code for obj_chase.Step if it helps any:

Code:
image_angle = point_direction(x,y,obj_player.x,obj_player.y);

if !place_meeting(x+lengthdir_x(2,point_direction(x,y,obj_player.x,obj_player.y)),y,obj_wall) && !place_meeting(x+lengthdir_x(2,point_direction(x,y,obj_player.x,obj_player.y)),y,obj_chase){
     x += lengthdir_x(2,point_direction(x,y,obj_player.x,obj_player.y));
}if !place_meeting(x,y+lengthdir_y(2,point_direction(x,y,obj_player.x,obj_player.y)),obj_wall) && !place_meeting(x,y+lengthdir_y(2,point_direction(x,y,obj_player.x,obj_player.y)),obj_chase){
     y += lengthdir_y(2,point_direction(x,y,obj_player.x,obj_player.y));
}if drone != 0{
    spin += 1;
}

if hp <= 0{
    instance_destroy();
}
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Okay, I can instantly see a few ways to speed that up a LOT! :)

Code:
if hp <= 0{
instance_destroy();
}
else
{
image_angle = point_direction(x, y, obj_player.x, obj_player.y);
var _xvec = lengthdir_x(2, image_angle);
var _yvec = lengthdir_y(2, image_angle);
if !place_meeting(x + _xvec, y, obj_wall) && !place_meeting(x + _xvec, y, obj_chase){
    x += _xvec;
    }
if !place_meeting(x, y + _yvec, obj_wall) && !place_meeting(x, y + _yvec, obj_chase){
    y += _yvec;
    }
if drone != 0{
   ++spin;
    }
}
Basically, try to NEVER use the same method more than once when you can use a local variable instead... in this case the lenghtdir_x/y and point_direction functions can all be calculated once and then then stored in variables. Also, you could create a parent object for all things that the code is checking and cut those "place_meeting" calls in half by only checking for the parent instead of the two objects you are checking for now.
 
E

EndermanTamer13

Guest
Thanks! It runs a lot smoother now! Although there is still some *slight* lag. Would I have to go through all my enemy objects and make variables for things I use more than once?
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Would I have to go through all my enemy objects and make variables for things I use more than once?
Indeed! Especially when using "expensive" functions (generally, any collision functions or distance and direction functions)... On a side note, also check your enemy AI. For example, if you have an enemy checking for the player every step, DON'T! Make it check in an alarm and then repeat the alarm every half second or something (stagger the alarms in the create event to a random time so they don't all go off at the same time and so spread the CPU load). Most AI doesn't require a line of sight check every step etc...
 
E

EndermanTamer13

Guest
Alright, I've modified the step event in the only other enemy that uses those kinds of things frequently in the step event:

Code:
if hp <= 0{
    instance_destroy();
}else{
    image_angle = point_direction(x,y,obj_player.x,obj_player.y);
    if instance_exists(obj_bullet) bullet = instance_nearest(x,y,obj_bullet);
    else bullet = -1;
    if instance_exists(obj_bullet) && distance_to_point(bullet.x,bullet.y) < 96{
        if spd < 4{
            spd += 1;
        }
        var bulletdir = point_direction(bullet.x,bullet.y,x,y);
        var xvec = lengthdir_x(spd,bulletdir);
        var yvec = lengthdir_y(spd,bulletdir);
    }else{
        if spd > 2{
            spd -= 0.1;
        }
        var xvec = lengthdir_x(spd,image_angle);
        var yvec = lengthdir_y(spd,image_angle);
    }if !place_meeting(x+xvec,y,obj_wall) && !place_meeting(x+xvec,y,obj_evade){
        x += xvec;
    }if !place_meeting(x,y+yvec,obj_wall) && !place_meeting(x,y+yvec,obj_evade){
        y += yvec;
    }
  
    if drone != 0{
        spin += 1;
    }
}
This code may look a lot more complicated, but that's because this enemy will attempt to dodge bullets. This code is actually simplified quite a lot! ;)
Just posting this one in case I did anything wrong...
 
Last edited by a moderator:
E

EndermanTamer13

Guest
Alright so when there are a lot of enemies present it hits the 80-100fps range... so something is still causing the game to run around 2/3 the speed it's supposed to...
Take note that it hits a consistent 120fps at all other times.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Time to check the profiler again! You might want to try using a top-down view as well as showing average values rather than total...;)
 
E

EndermanTamer13

Guest
Time to check the profiler again! You might want to try using a top-down view as well as showing average values rather than total...;)
Alright so I checked it and here's a screenshot:



Looks like place_meeting is still being used a heck of a lot...

Take note that for some reason the game ran at like 20fps when the Profiler was running, and went kind of back to normal when I turned it off, so the numbers shouldn't be that extreme. Also, I've been paying more attention, and the lag only seems to appear when obj_chase shows up. Like I said, the lag is a lot less significant now, but I'd still like to remove it.

I can't diminish how often place_meeting is used in obj_chase, as I can't add obj_chase and obj_wall under some collision parent, because the game requires obj_enemyparent to be obj_chase's parent, and obj_wall has to NOT have it as its parent, but at the same time certain children of obj_enemyparent need to not be included in these collisions, so there's no way I can arrange this new parent object that will work. :(
 
Last edited by a moderator:

Nocturne

Friendly Tyrant
Forum Staff
Admin
Are you using precise or rectangular collisions? If you are using precise, then that'll be slowing down the place_meeting functions a LOT... You could also try reconfiguring your code to use instance_place and "all", then parse the returned value. So something like:

Code:
var _inst = instance_place(x + _xvec, y, all);
if _inst != noone
{
switch(_inst.object_index)
    {
    case obj_Wall:
    case obj_Chase:
        break;
    default: x += _xvec;
    }
}
So, something like that? It MAY be slower, since it's checking all the instances, but I suspect it'll improve things. Another good way to optimise stuff, is to remove all the wall objects completely! You can do this by adding them into a DS grid and then destroying them (use tiles to show where they were), then do collision checks against the ds grid rather than the instance. Skein does this, and it's very fast. You could also try removing the checks for the chase object and simply dealing with them in a collision event.
 
E

EndermanTamer13

Guest
Alright, I could try that.

Another good way to optimise stuff, is to remove all the wall objects completely! You can do this by adding them into a DS grid and then destroying them (use tiles to show where they were), then do collision checks against the ds grid rather than the instance. Skein does this, and it's very fast.
This could work, but some of the enemies bounce off walls, and I'm not sure how I could get them to bounce off a DS grid.

You could also try removing the checks for the chase object and simply dealing with them in a collision event.
The reason I have the code checking for the chase object is that if I don't those kinds of enemies will start being inside each other, and could start getting stuck, and I don't want that.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
This could work, but some of the enemies bounce off walls, and I'm not sure how I could get them to bounce off a DS grid.
It's really easy! Just divide the goto position by the grid resolution (32 or 64 or 8 or whatever), then get the value of the grid... if the value corresponds to a wall, perform the bounce action. So, something like:

Code:
var _gx = floor((x + _xvec) / 32);
var _gy = floor((y + _yvec) / 32);
var _inst = ds_grid_get(collision_grid, _gx, _gy);
if _inst > 0
{
// COLLISION! So deal with it here...
}
Again, my game Skein has NO collision events and NO collision checks between instances, it's all done with ds_grids. :)
 
E

EndermanTamer13

Guest
It's really easy! Just divide the goto position by the grid resolution (32 or 64 or 8 or whatever), then get the value of the grid... if the value corresponds to a wall, perform the bounce action. So, something like:

Code:
var _gx = floor((x + _xvec) / 32);
var _gy = floor((y + _yvec) / 32);
var _inst = ds_grid_get(collision_grid, _gx, _gy);
if _inst > 0
{
// COLLISION! So deal with it here...
}
Again, my game Skein has NO collision events and NO collision checks between instances, it's all done with ds_grids. :)
The thing is bouncing off things is the only thing I use D&D for, because I never could figure out how to do it right in code, so...
 
E

EndermanTamer13

Guest
Are you using precise or rectangular collisions? If you are using precise, then that'll be slowing down the place_meeting functions a LOT...
Also, what do you mean by precise? Do you mean like the masks? If so, yes, they were precise, and I changed them to elliptical, because the masks I used were circles anyway. Will that work?
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Not a lot you can do... optimise other areas of the code to mitigate the hit from the collision check.
 
E

EndermanTamer13

Guest
Not a lot you can do... optimise other areas of the code to mitigate the hit from the collision check.
Also I tried this code:

Code:
var _inst = instance_place(x + _xvec, y, all);
if _inst != noone
{
switch(_inst.object_index)
    {
    case obj_Wall:
    case obj_Chase:
        break;
    default: x += _xvec;
    }
}
and I believe that, since the instance running the check is an obj_chase, it will always find itself in that range, and therefore not move, and I've seen this. I put the code into the game and now they don't even move. I get the feeling I should put a check for either id or self in there, but I have to figure out where. Also, is something like !self a thing?
 
Last edited by a moderator:

Nocturne

Friendly Tyrant
Forum Staff
Admin
Just do this:

Code:
if _inst != noone && _inst != id
That should work.

EDIT: Actually, I'm pretty sure that instance_place won't check for itself, so something else must be the issue...

EDIT EDIT! Ah! Yeah, okay, the code should be...
Code:
var _inst = instance_place(x + _xvec, y, all);
if _inst != noone
{
switch(_inst.object_index)
   {
   case obj_Wall:
   case obj_Chase:
       break;
   default: x += _xvec;
   }
}
else x += _xvec; // This is required for when no instances are found at the position!
 
Last edited:
E

EndermanTamer13

Guest
Just do this:

Code:
if _inst != noone && _inst != id
That should work.

EDIT: Actually, I'm pretty sure that instance_place won't check for itself, so something else must be the issue...

EDIT EDIT! Ah! Yeah, okay, the code should be...
Code:
var _inst = instance_place(x + _xvec, y, all);
if _inst != noone
{
switch(_inst.object_index)
   {
   case obj_Wall:
   case obj_Chase:
       break;
   default: x += _xvec;
   }
}
else x += _xvec; // This is required for when no instances are found at the position!
You're a genius! And also, I get the feeling I should put that code in a script, because like 6 different objects use that same code, and I'd rather not copy and paste all around! ;)
 
E

EndermanTamer13

Guest
Does that improve the framerate? And yeah, use a script!
Well, I'm trying to wrap my head around why it gives me a fatal error saying that xinst or yinst was not defined now that I made it a script. :confused:
 
E

EndermanTamer13

Guest
Does that improve the framerate?


Something's very wrong with obj_split and obj_evade's instance_place usage... especially obj_split...

If it helps, here's obj_split's step event:

Code:
image_angle = point_direction(x,y,obj_player.x,obj_player.y);
var xvec = lengthdir_x(3,image_angle);
var yvec = lengthdir_y(3,image_angle);
var xinst = instance_place(x+xvec,y,all);
var yinst = instance_place(x,y+yvec,all);

scr_collide(xinst,yinst,xvec,yvec);
It seems like the "torch" of the lag-causing enemy has been passed to obj_split now. It's step event is very simple, so I am EXTREMELY confused as to why this happened. Maybe it's because this enemy has a "speed" of 3 instead of the usual 2? I doubt it though.
 
Last edited by a moderator:

andev

Member
Ellipses are slow too, but better than precise! Ideally you want to use a rectangular mask...
collision_elipse might be slow, but to check a circle you just have to check if point_distance is smaller than the collision radius which can't be that slow?
 
E

EndermanTamer13

Guest
Okay, so I ran a few more tests with the Profiler, and obj_split is not the sole cause of lag. Any of the three enemies that frequently spawned that use instance_place will cause heavy lag. Like, it dropped from around 80-90fps without these enemies to as low as around 20fps while they were in the room. It appears that RNG had it that obj_split was created quite a lot during that test, because in the tests I just ran, those three objects were the cause, not just obj_split.

Point is, the swap from place_meeting to instance_place changed nothing, and now I have to find a way to reduce the usage of instance_place as much as possible. So, I need a way to code it so xinst and yinst can still be used as they need, but doesn't call instance_place every step.

EDIT: I swapped out instance_place with instance_position, and it removes a lot of the lag, but there is still some lag. Also, I've noticed that when enemies bunch up together next to a corner, the enemies can be pushed by the other enemies through the corner in the wall, and I don't want that.
 
Last edited by a moderator:

Simon Gust

Member
Have you tried the deactivation / activation of instances method. I saw that your view covers most of the room so it probably doesn't do much.
The Idea is to exclude instances from the lookup loop for functions like place_meeting, instance_place etc. if they are out of view.
 
E

EndermanTamer13

Guest
Have you tried the deactivation / activation of instances method. I saw that your view covers most of the room so it probably doesn't do much.
The Idea is to exclude instances from the lookup loop for functions like place_meeting, instance_place etc. if they are out of view.
Well, I still want the enemies to be able to shoot at you even though they are off-screen. It adds more difficulty to the game, I think. So, I don't think I could do that.
 

Simon Gust

Member
Well, I still want the enemies to be able to shoot at you even though they are off-screen. It adds more difficulty to the game, I think. So, I don't think I could do that.
Ok, can you show your code blocks? I'm thinking of something.
How many instances are there exactly? And what do they interact with?
 
E

EndermanTamer13

Guest
How many instances are there exactly? And what do they interact with?
Well, the game doesn't spawn enemies if there are more than 100 currently in the room, so up to around 120-130 at any given time. And the enemies will follow the player, cannot pass through walls, cannot pass through each other, will either lose HP or get destroyed when hit by bullets, and some of them will even avoid bullets. That's all I can think of that they interact with.

Ok, can you show your code blocks? I'm thinking of something.
Which code blocks? All of the code blocks in the object, or just one event, or the whole game?
 

Simon Gust

Member
Well, the game doesn't spawn enemies if there are more than 100 currently in the room, so up to around 120-130 at any given time. And the enemies will follow the player, cannot pass through walls, cannot pass through each other, will either lose HP or get destroyed when hit by bullets, and some of them will even avoid bullets. That's all I can think of that they interact with.
That does sound very cpu heavy. The part alone where enemies avoid each other. Thats already 10000 collision functions per frame. Then normal collision another 100 * the amount of collision instances collision functions. Then in addition some enemies avoiding bullets another smart enemies * bullet instances functions per frame. No pc will keep up with that.

Which code blocks? All of the code blocks in the object, or just one event, or the whole game?
The most important code such as motion, collision and interaction.
 
E

EndermanTamer13

Guest
The most important code such as motion, collision and interaction.
Okay, well here is an example, the obj_evade, which dodges bullets. Bullet dodging and some motion is here, in the step event:

Code:
if hp <= 0{
    instance_destroy();
}else{
    image_angle = point_direction(x,y,obj_player.x,obj_player.y);
    if instance_exists(obj_bullet) bullet = instance_nearest(x,y,obj_bullet);
    else bullet = -1;
    if instance_exists(obj_bullet) && distance_to_point(bullet.x,bullet.y) < 96{
        if spd < 4{
            spd += 1;
        }
        var bulletdir = point_direction(bullet.x,bullet.y,x,y);
        var xvec = lengthdir_x(spd,bulletdir);
        var yvec = lengthdir_y(spd,bulletdir);
    }else{
        if spd > 2{
            spd -= 0.1;
        }
        var xvec = lengthdir_x(spd,image_angle);
        var yvec = lengthdir_y(spd,image_angle);
    }
    var num = 16/spd;
    var xinst = instance_position(x+(xvec*num),y,all);
    var yinst = instance_position(x,y+(yvec*num),all);
  
    scr_collide(xinst,yinst,xvec,yvec);
  
    if drone != 0{
        spin += 1;
    }
}
The rest of motion and some collisions are here, in scr_collide:

Code:
///scr_collide(xinst,yinst,xvec,yvec)
xx = argument0;
yy = argument1;
xv = argument2;
yv = argument3;

if xx != noone && xx != id{
    switch (xx.object_index){
        case obj_wall:
        case obj_chase:
            break;
        default: x += xv;
    }
}else{
    x += xv;
}if yy != noone && yy != id{
    switch (yy.object_index){
        case obj_wall:
        case obj_chase:
            break;
        default: y += yv;
    }
}else{
    y += yv;
}
The collision events, which are all the same:

Code:
x += lengthdir_x(5,point_direction(other.x,other.y,x,y));
y += lengthdir_y(5,point_direction(other.x,other.y,x,y));
I don't know if there are any other code blocks you might need, so I'll leave it at that.

Take note that as of right now it stays at 120fps when there are little or no chasing enemies in the room, and dips to around 80-100fps when there are many of these chasing enemies in the room.
 
Last edited by a moderator:

Simon Gust

Member
I have some optimizations but it seems to be quite fluent with what I've just tested. Can you share the file? There seems to be something hidden and evil of what you told us.
 
This:

Code:
if instance_exists(obj_bullet) bullet = instance_nearest(x,y,obj_bullet);
   else bullet = -1;
   if instance_exists(obj_bullet) && distance_to_point(bullet.x,bullet.y) < 96{
Can be optimised to this:
Code:
bullet = instance_nearest(x,y,obj_bullet);

if ( bullet != noone)
{
    if ( distance_to_point(bullet.x,bullet.y) < 96
    {

    }
}
which gets rid of one instance_exists() call, because instance_nearest will already return "noone" if there are no bullets.

This:
Code:
x += lengthdir_x(5,point_direction(other.x,other.y,x,y));
y += lengthdir_y(5,point_direction(other.x,other.y,x,y));
Can be optimised:
Code:
var dir = point_direction(other.x, other.y, x, y);
x += lengthdir_x(5, dir);
y += lengthdir_y(5, dir);
This will halve the number of calls to point_direction.
 
E

EndermanTamer13

Guest
This:

Code:
if instance_exists(obj_bullet) bullet = instance_nearest(x,y,obj_bullet);
   else bullet = -1;
   if instance_exists(obj_bullet) && distance_to_point(bullet.x,bullet.y) < 96{
Can be optimised to this:
Code:
bullet = instance_nearest(x,y,obj_bullet);

if ( bullet != noone)
{
    if ( distance_to_point(bullet.x,bullet.y) < 96
    {

    }
}
which gets rid of one instance_exists() call, because instance_nearest will already return "noone" if there are no bullets.

This:
Code:
x += lengthdir_x(5,point_direction(other.x,other.y,x,y));
y += lengthdir_y(5,point_direction(other.x,other.y,x,y));
Can be optimised:
Code:
var dir = point_direction(other.x, other.y, x, y);
x += lengthdir_x(5, dir);
y += lengthdir_y(5, dir);
This will halve the number of calls to point_direction.
Thanks! I'll change those.
 

Simon Gust

Member
So, the debugger is kind of lying if you don't have it set on combined.
upload_2018-1-6_19-53-12.png

I am strongly assuming that the HandleCollision tab is for all the collision events only. These are optimized however so I'm not sure how to help. Either way, instead of trying to optimize the code, try to restructure.
 
E

EndermanTamer13

Guest
Either way, instead of trying to optimize the code, try to restructure.
Well, this is the only structure I could come up with that worked. I tried simply using direction and speed, but there was no way to get them to stop ending up stuck inside each other.
 

Simon Gust

Member
I am also seeing that the player's sprite is a cirlce. I changed it to full image and I got another 30 fps.
I also tempered with the script scr_collide.
Code:
xx = argument0;
yy = argument1;
xv = argument2;
yv = argument3;

if xx == id || xx == noone {
    x += xv;
}
if xx == id || yy == noone {
    y += yv;
}
I gave obj_enemyparent and obj_wall another parent. called obj_collide. I will call obj_collide instead of "all" in the instance_position functions such as
Code:
var xinst = instance_position(x+(xvec*num),y, obj_collide);
var yinst = instance_position(x,y+(yvec*num), obj_collide);
Giving me 300 fps now.
 
E

EndermanTamer13

Guest
I gave obj_enemyparent and obj_wall another parent. called obj_collide. I will call obj_collide instead of "all" in the instance_position functions such as
The thing is, I don't want these chasing objects to interact with obj_scout, obj_bounce, obj_tankscout, and obj_tankbounce AT ALL. I want them to just pass right through like they're not there.
 
Top