mp path causing significant frame drops

O

ObserverOfSin

Guest
Hey all. nice to meet you.
when monitoring fps_real I notice significant drops in frames with each enemy I create.
can someone please take a look at my code to see if they know of more ways to optimize performance?
I am by no means adept at gml.


GML:
direction = angle
ii +=1
xx = path_get_point_x(path, pos);
yy = path_get_point_y(path, pos); 
 
mp_grid_clear_all(global.grid);   /// add instances of self to grid
 with enemyparent_obj
    {
    if (id != other.id) mp_grid_add_instances(other.grid, id, 0);
    }
 
 if ii >= 30   /// every 30 frames update coordanites  and set ii to 0
{ 
 
   if point_distance(x, y, xx, yy) < 8
   {
   if pos < 2
   {
   pos  += 1 
   }
   else
   {
   pos = 1
   xx = irandom(30)
   yy = irandom(30)
   }
   } 
 ii = 0  
}
if atend = 0    
{ 
i +=1
dir = point_direction(x,y,xx,yy)
angle += median(-2, 2, ((((dir - angle) mod 360) + 540) mod 360) - 180);
mp_potential_step(xx, yy, 1, false);
if  ii = 0 and  instance_exists(target)
  {
  mp_grid_path(global.grid,path,x,y,target.x,target.y,true)
  }
  else
  {
  xx = irandom(30)
  yy = irandom(30)
  }

 {
 if i >= 30   /// every 30 frames check if a new target is in range
 {  
   newtarget = instance_nearest(x,y,friendlyparent_obj) 
   if distance_to_point(newtarget.x, newtarget.y) < sight  
   {
   target = newtarget
   pos =1
   i = 0
   }
   i = 0
 }
   if distance_to_object(target) < 15
   {
   atend = 1
   sprite_index = second
   image_speed = aspeed
   }
 }
}

if sprite_index = attack and (image_index+image_speed >= image_number)/// inflict damage
  {
  if instance_exists(target)   {target.hitpoints -= damage}
  if target.hitpoints <= 0 {with(target)instance_destroy()}
  }
  
if  !mp_grid_path(global.grid,path,x,y,target.x,target.y,true)                 /// Target motheship when target dies
 {
 xx = irandom(300)
 yy = irandom(300)
 return false;
 }
else
  {
  sprite_index = main
  image_speed = 0.2
  target  = mothership_obj
  pos = 1
  atend = 0
  return true;
  }
  
  if sprite_index = second    /////turn when attacking
  {
  dir = point_direction(x,y,target.x,target.y)
  angle += median(-2, 2, ((((dir - angle) mod 360) + 540) mod 360) - 180);
  }
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
You are clearing the grid and re-filling it every step for every instance. SO if you have 10 enemies in the room, that's 10 times each step that the grid is being recreated. You should only need to recreate the grid once per room, or - at most - once per time something in the room changes. This is the first bottleneck I see in the code... Ideally you want to create the grid in a controller object, and then only change the grid contents if something in the room changes, not every step.

Now, that brings me to the second issue... You are putting DYNAMIC objects into the grid... What I mean is that it seems you are using the grid to hold the collision data for objects that are moving (enemies), which is why you are doing the above clearing/recreating every step. This is really NOT how grids should be used... they are designed to be used with static objects that don't generally move (think walls!). That said, you CAN use them to do what you're doing, but (as mentioned above) only clear and recreate the grid ONCE per step in a controller, not in every instance. While this is still not a great idea, it will greatly improve performance. Oh, and you don't mention the resolution of the grid! A grid with a 4x4px cells will be MUCH slower than a grid with 64x64px cells. So maybe you could optimise there too?

The third issue I see is that you are creating a path every step through the grid when you call "if !mp_grid_path" near the end of the code. Again, creating a path every step is not good practice. You would be better putting that check in an alarm and only calling it every second or half-second or whatever works for your game. Alternatively, you could wrap this in a variable check that is only called when the "target" dies and you are switching to the mothership.

Finally, you really want to go through the code and try and structure it so that only ONE section is actually being run at a time for a specific reason. At the moment it looks like you have a lot unneeded checks in there that could be avoided using a state machine, or better structured if...else statements.
 

NightFrost

Member
Adding dynamic content like enemies to a mp grid creates various problems. One good example is a long tunnel that is the shortest path to player, but its width is one enemy. When pathfinding, enemies will head towards this shortest path, but when the first one reaches it everyone else turn away to other paths. This is because the enemy will act like a static block for all the others. In other words, you cannot form queues or other intelligent group behavior with pathfinding like that. If a group of enemies somehow found themselves in a long narrow tunnel like that anyway, only the first and last enemy in line would be able to pathfind and act as blocks for the rest, which won't move at all as they're finding zero paths to goal.

For dynamic local avoidance you'd instead look into other methods, such as steering behaviors.
 

NightFrost

Member
Or split characters and obstacles to diferent "layers".
If you mean running two pathfinds with mp grids that have been filled with different obstacles, that won't necessarily solve problems. A grid with just walls will propose a path through enemies, and a grid just enemies will propose a path through a wall. Besides having to reconcile two different propositions, there's no good answers when both propositions are wrong (through wall and through enemy). That's also double the number of pathfind calls, which is not necessarily cheap.
 

4i4in

Member
A grid with just walls will propose a path through enemies,
But this can be solved by diferent part of code that works with dynamic situation.

Besides having to reconcile two different propositions, there's no good answers when both propositions are wrong (through wall and through enemy). That's also double the number of pathfind calls, which is not necessarily cheap.
Dynamic obstacles can be avoided using greed. They will move away sooner or later. Do we need super logical agent? Enemy can be somehow stupid.
 

NightFrost

Member
But this can be solved by diferent part of code that works with dynamic situation.
Which is why I mentioned steering behaviors. They way I've tested out pathfinding is by creating a heatmap, which is a Dijkstra's radiating out through the grid from player location. The distance data is then used to calculate a vector for each cell (neighbour distances [left - right, top - bottom]) so they can be followed back to player. So no matter the number of enemies, the heatmap is generated once every time player switches a grid cell, and it guides all the enemies to player. The proposed direction from this is taken as the direction an agent wants to move in steering behaviors. Steering behaviors have some problems in certain scenarios, so I've moved to testing context steering. I'm not totally satisfied with some results, and its been an on-and-off project so I haven't been back to have a look at those problems.

(A Dijkstra running in GML is slower than the builtin A* so depending on number of enemies and number of concurrent pathfinding goals, mp grid will be faster.)
 
Top