GMS 2 [Please Help] Enemy pathfinding problems :(

Hi, GameMaker Community.
I'm making a top down game, but the enemies that are supposed to follow the player, sometimes run into walls and obstacles.
I've temporarily fixed this by doing the following:

// If player is near Enemy object then
target_x - is player postition x
target_y - is player postition y

// Otherwise
target_x_fr - is freeroam postition x
target_y_fr - is freeroam postition y

[ Enemy ] Step Event
Code:
/// Stop from overlapping obstacles & walls
if !(mp_grid_path(grid, path, x, y, target_x, target_y, 1))
    {
        while (place_meeting(x, y, obj_obstacle))
        {
            target_x = x;
            target_y = y;

            target_x_fr = -32;
            target_y_fr = -32;

            x -= lengthdir_x(1, direction);
            y -= lengthdir_y(1, direction);
        }
    }
[ Enemy ] End Step Event
Code:
if (collision_line(xprevious, yprevious, x, y, obj_obstacle, 1, 0))
{
    event_perform(ev_collision, obj_obstacle);
}
Also, another problem. When an enemy is near another one, I want them to avoid each other but currently they don't do that.. Previously they just overlapped with each other.
I (kinda) got it working with the 'Enemy' object but sadly it just works with only other enemies that are not the same object as the one that is checking for nearby enemies (I think it's caused by the constant other enemy disabling, for example:
When Enemy 1 is near (Enemy 1 or Enemy 2 etc) then it works for only other Enemy 2. So if both (Enemy 1 and other Enemy 1) are checking for potential collision conflicts (running into each other) then they'll end up looping those checks forever because both are checking those simultaneously.. )

I don't really know how to exactly explain it more easily.

Here is the code I'm using to disable nearby other potential unwanted Enemy overlappings:
[ Grid ] Create Event ------------
Code:
var cell_w = cell_size;
var cell_h = cell_size;

var hcells = room_width div cell_w;
var vcells = room_height div cell_h;

/// Create the grid for all path functions using objects
global.grid = mp_grid_create(0, 0, hcells, vcells, cell_w, cell_h);

// List all OBJECTS to collide with
c_obj[0] = obj_enemy_01;
c_obj[1] = obj_enemy_02;

grid = global.grid;
[ Grid ] Step Event ------------
Code:
checker = obj_enemy_01;
var i = 0;
if ( i < array_length_1d(c_obj) )
{
    if (point_distance( checker.x, checker.y, c_obj.x, c_obj.y ) < 70) // if other enemy object is closer than 70 then reload all paths
    {
        scr_reload_paths(grid); // reload all objects on grid
        if (checker.id != c_obj.id) // check if ID is not the same as other's
        { mp_grid_add_instances(grid, c_obj.id, 0); }
        else i ++;
    }
    else
    {
        scr_reload_paths(grid); // reload all objects on grid
    }
}
[ scr_reload_paths() ] Script ------------------
Code:
var path_grid = argument0;

mp_grid_clear_all(path_grid);

if (instance_exists(obj_obstacle)) mp_grid_add_instances(path_grid, obj_obstacle, 1);
if (instance_exists(obj_door)) mp_grid_add_instances(path_grid, obj_door, 1);
Here is the Enemy object's whole code:
[ Enemy ] Create Event ------------
Code:
/// Create paths
path = undefined;

/// Setup MAIN freeroam coordinates
target_x_fr = x + irandom_range(-100,100);
target_y_fr = y + irandom_range(-100,100);

/// Setup target's coordinates
target_x = obj_player.x;
target_y = obj_player.y;

/// Setup speeds
spd_default = 18;
spd = spd_default;

/// Range from player
range_from_player = distance_to_object(obj_player); // get distance between Enemy and Player
follow_range = 320; // this is the max target following range

/// Setup FREE ROAM cooldown timers
fr_timer_max = 2;
fr_timer = fr_timer_max;

/// FREE ROAM coordinates - only used to give temporary coordinates to 'MAIN freeroam coordinates'
x_fr = -32;
y_fr = -32;
[ Enemy ] Step Event ------------
Code:
/// Setup grid and path for future reference
grid = global.grid;
path = path_add();

/// Setup Free Roam target
nearest_obstacle = obj_obstacle;
nearest_wall = instance_find(nearest_obstacle, random(instance_number(nearest_obstacle) - 1));
distance_wall = distance_to_object(nearest_wall);
if (fr_timer < 1)
{  
    if (path_index == -1)
    {
        // Reset free roam
        x_fr = -32;
        y_fr = -32;
        while (x_fr == 0 and y_fr == 0) // While temporary free roam coordinates are not set yet
        {
            // Set temporary free roam coordinates to a random range
            x_fr = irandom_range(-300,300);
            y_fr = irandom_range(-300,300);
        }
        // Add new coordinates to x, y positions
        target_x_fr = x + x_fr;
        target_y_fr = y + y_fr;
        // Reset free roam timer
        fr_timer = fr_timer_max;
    }
}
else
{
    fr_timer -= 0.1;
}

/// Setup Player target
target_x = obj_player.x;
target_y = obj_player.y;

/// Follow target (Player)
if (mp_grid_path(grid, path, x, y, target_x, target_y, 1)) // if can reach Player on grid
{
    if (distance_to_object(obj_player) <= follow_range*2)
    {
        path_start(path, spd+7, path_action_stop, 0);
    }
}
else if (mp_grid_path(grid, path, x, y, target_x_fr, target_y_fr, 1)) // if can reach free roam coordinates on grid
{
    if (distance_to_object(obj_player) > follow_range) // if Player is out of Enemy's range
    {
        path_start(path, spd/4, path_action_stop, 0);
    }
    else
    {
        path_start(path, spd, path_action_stop, 0);
    }
}
else
{
    path_end();
}

/// Set Enemy's angle to the direction it's moving towards
if (path_index != -1) angle = round(direction);

/// When Enemy reaches the Player, stop the movement and reset all coordinate variables
if ( place_meeting(x, y, obj_player) )
{
    path_end();
    target_x = x;
    target_y = y;
  
    target_x_fr = -32;
    target_y_fr = -32;
}
[ Enemy ] End Step Event ------------
Code:
if (collision_line(xprevious, yprevious, x, y, obj_obstacle, 1, 0))
{
    event_perform(ev_collision, obj_obstacle);
}
Sorry for such a long and messy code..
So, what could possibly be causing these problems?
 
Last edited:
Though, I don't think that's what is causing the neverending loop?
What would you do about that problem?
Note: When checker object finds another enemy then it will stop the other enemy - set it to an obstacle in the global.grid
Code:
checker = obj_enemy_01;
for (var i = 0; i < array_length_1d(c_obj); i++)
{
    if (point_distance( checker.x, checker.y, c_obj.x, c_obj.y ) < 70)
    {
        scr_reload_paths(grid);
        if (checker.id != c_obj.id)
        { mp_grid_add_instances(grid, c_obj.id, 0); }
        else i ++;
    }
    else
    {
        scr_reload_paths(grid);
    }
}
Is there some if statement I'm missing?
Here is the problem shown in-game
1.png
 
Last edited:

Nidoking

Member
for (var i = 0; i < array_length_1d(c_obj); i++)
{
if (point_distance( checker.x, checker.y, c_obj.x, c_obj.y ) < 70)
is c_obj an array, an object, or an instance? Presumably, you must mean c_obj[ i ] in many places. In fact, you're not actually using i anywhere. You're just incrementing it sometimes, now within a for loop that uses i as its index.

EDIT: Ah, I see, the forum ate the subscript because you didn't use a CODE block.

EDIT 2: How many instances of obj_enemy_01 and obj_enemy_02 are there? Because you're adding the object indices to an array, but it looks like you're expecting the array to somehow magically have the indices of all of the instances of those objects. You've got a whole lot of object index dot variable in there, which is bad.
 
is c_obj an array, an object, or an instance? Presumably, you must mean c_obj[ i ] in many places. In fact, you're not actually using i anywhere. You're just incrementing it sometimes, now within a for loop that uses i as its index.

EDIT: Ah, I see, the forum ate the subscript because you didn't use a CODE block.

EDIT 2: How many instances of obj_enemy_01 and obj_enemy_02 are there? Because you're adding the object indices to an array, but it looks like you're expecting the array to somehow magically have the indices of all of the instances of those objects. You've got a whole lot of object index dot variable in there, which is bad.
So, should I make an array for each enemy?
 

Nidoking

Member
You can make one array for all of the enemies. You just have to put instance IDs into it if you're going to treat the contents of the array as instance IDs. If you put object IDs in, then what you get out are object IDs. You need to treat them as object IDs. Arrays can't magically turn one type of thing into another type of thing.
 
My objective here is the following: Use an array to loop through all of the instances of enemy objects, and if any of the enemy instances find another instance of ANY enemy, then add the nearby other enemies into the global.grid as obstacles which to avoid (much like the main obstacles - doors, walls etc)

btw, I'm using a dynamic grid to change the map for different paths (to block the enemies from entering for example)
scr_reload_paths() is the script which reloads the whole global.grid - reloads as deletes and adds all the needed objects
 

Nidoking

Member
The statement of your objective is noted. You may continue to ponder the answer I have provided, as it will not change.
 
The statement of your objective is noted. You may continue to ponder the answer I have provided, as it will not change.
After some pondering, I've come up with this
Inside obj_grid Step Event:
Code:
enemy = undefined;
obj = obj_enemy_01;
for (var i = 0; i < instance_number(obj); i += 1)
{
    if (instance_find(obj,i) != undefined) enemy[i] = instance_find(obj,i);
}

enemy_other = undefined;
obj = obj_enemy_01;
for (var o = 0; o < instance_number(obj); o += 1)
{
    if (instance_find(obj,o) != undefined) enemy_other[o] = instance_find(obj,o);
}
Code:
obj_other = obj_enemy_01;

for (var i = 0; i < array_length_1d(enemy); i++)
{
    for (var j = 0; j < instance_number(obj_other); j ++)
    {
        if (point_distance( enemy[i].x, enemy[i].y, enemy_other[j].x, enemy_other[j].y ) < 70)
        {
            scr_reload_paths(grid);
            if (enemy[i].id != enemy_other[j].id)
            {
                mp_grid_add_instances(grid, enemy_other[j], 1);
            }
            else break;
        }
        else
        {
            scr_reload_paths(grid);
        }
    }
}
I'd say this is highly unoptimized but.. it kinda works
I'd really want to get it somewhat working in the way that minecraft handles entity collisions - push nearby (or literally overlapping entities) aside each other.
 
Last edited:

Nidoking

Member
You should probably learn how to use with. It's in the Manual. As an example, this would populate your "enemy" array:

GML:
var i = 0;
with (obj_enemy_01)
{
  enemy[i++] = id;
}
You could also do enemy = id; i++; if you're more comfortable putting the increment operation on its own line. But I feel that any time you're using a for loop and instance_find, it's an unnecessarily complicated replacement for a simple with.
 
You should probably learn how to use with. It's in the Manual. As an example, this would populate your "enemy" array:

GML:
var i = 0;
with (obj_enemy_01)
{
  enemy[i++] = id;
}
You could also do enemy = id; i++; if you're more comfortable putting the increment operation on its own line. But I feel that any time you're using a for loop and instance_find, it's an unnecessarily complicated replacement for a simple with.
Yes, that definitely seems to be a better way of doing this.
Code:
enemy = undefined;
obj = obj_enemy_01;
var i = 0;
with (obj)
{
  enemy[i++] = id;
}

enemy_other = undefined;
obj = obj_enemy_01;
var i = 0;
with (obj)
{
  enemy_other[i++] = id;
}
But what should I do change in this?
Code:
obj_other = obj_enemy_01;

for (var i = 0; i < array_length_1d(enemy); i++)
{
    for (var j = 0; j < instance_number(obj_other); j ++)
    {
        if (point_distance( enemy[i].x, enemy[i].y, enemy_other[j].x, enemy_other[j].y ) < 70)
        {
            scr_reload_paths(grid);
            if (enemy[i].id != enemy_other[j].id)
            {
                mp_grid_add_instances(grid, enemy_other[j], 1); //with(enemy_other[j]) path_end();
                with(enemy_other[j])
                {
                    x -= lengthdir_x(10, direction);
                    y -= lengthdir_y(10, direction);
                }
            }
            else break;
        }
        else
        {
            scr_reload_paths(grid);
        }
    }
}
 

Nidoking

Member
You did read the Manual pages for with, correct? It loops over all instances of an object. You don't need for loops at all for this.
 
Top