GML Is it possible to pull information from a path then move it towards each point manually?

JEMcG

Member
Hello!

So I have been wrestling for some time with pathing. I'm trying to make a game where the player can choose whether to use keyboard or controller movement (which works just fine) or mouse movement.

For mouse movement so far I am using an mp_grid and mp_grid_path to do the movement.
This is working really well for finding where the player needs to go and avoiding abjects. I have a script set up for if the player clicks within a collision etc...
So I'd like to keep this system for finding the path the player needs to take.

My main issue is that when the player (or AI) follows these paths, it tends to be quite janky, in terms of the animation.
Below is the 8-directional code I'm using (onpath is what I'm using to signal they are moving using the mouse):


GML:
//Work out anim frame
if(hInput != 0 or vInput != 0 or onpath){
    y_frame = floor(dir/45);
    //Increment frame for animation (if we make an idle anim take this out of IF)
    x_frame += anim_speed/room_speed;
    if(x_frame >= anim_length) x_frame = 0;
} else {
    x_frame = 0.9;  
}  

var xx = x-x_offset;
var yy = y-y_offset;

//Draw Sprite
draw_sprite_part(spr_mc, 0, floor(x_frame)*frame_width, y_frame*frame_height, frame_width, frame_height, xx, yy);
When the character follows they path, especially when going on a diagonal, they tend to flit between two frames rapidly which looks rather bad. I have tried turning the "allow diagonal" on and off within the mp_grid_path code, but neither work.
I don't have this issue with keyboard or controller movement, which is based around point_direction and then moving the player towards it.

I have searched but I can't tell if there's anyway to pull the points from an mp_grid_path and then move towards them as I would were I using key presses?
This seems like it would be the cleanest way to me, although perhaps there's a more elegant way?

I'll post my movement code below also in case that helps:


Code:
//----------------------MOVEMENT
//KB or Controller
//Inputs
hInput = global.right - global.left;
vInput = global.down - global.up;

if(hInput != 0 or vInput != 0){
    //reset path
    if(path_exists(wpath)){
        path_delete(wpath);  
        finalx = -1;
        finaly = -1;
        onpath = false;
        wpath = -1;
    }
   
    //Calculate direction (including changing diagonal speed)
    dir = point_direction(0, 0, hInput, vInput);
    //Make changes for isometric movement
    if(dir == 135) or (dir == 315){ iso_buff = 18.5}
    else if(dir == 225) or (dir == 45){ iso_buff = -18.5 }
    else { iso_buff = 0;}  
   
    //Calculate movement
    moveX = lengthdir_x(spd, dir + iso_buff);
    moveY = lengthdir_y(spd, dir + iso_buff);
   
    //----Collisions
    //Horizontal
    if(place_meeting(x+sign(moveX), y, obj_col)){
        repeat(abs(moveX)){
            if(!place_meeting(x+sign(moveX), y, obj_col)){ x += sign(moveX);}
            else { break;}  
        }
        moveX = 0;
    }  
    //Vertical
    if(place_meeting(x, y+sign(moveY), obj_col)){
        repeat(abs(moveY)){
            if(!place_meeting(x, y+sign(moveY), obj_col)){ x += sign(moveY);}
            else { break;}  
        }
        moveY = 0;
    }  
   
    //Apply Movement
    x += moveX;
    y += moveY;
}

//Mouse Movement
if(mouse_check_button_pressed(mb_left)){
    //wipe previous path
    if(path_exists(wpath)){
        path_end();
        path_clear_points(wpath);
        finalx = -1;
        finaly = -1;
        onpath = false;
        wpath = -1;
    }
    //Get mouse location
    var mx = mouse_x;
    var my = mouse_y;
    //Is this on a collision?
    if(position_meeting(mx, my, obj_col)){
        var newsp = space_check(mx, my);
        //change mx/y to new location
        mx = newsp[0];
        my = newsp[1];
    }  
    //move to mx/my
    wpath = path_add();
    mp_grid_path(global.mgrid, wpath, x, y, mx, my, true);
    path_start(wpath, spd, path_action_stop, false);
    finalx = mx;
    finaly = my;
    onpath = true;
}
//---Animations
//when path ends, turn off anims
if(abs(x-finalx)<1 and abs(y-finaly)<1){ //ensure you're less than 1 pixel from target
    path_delete(wpath);  
    finalx = -1;
    finaly = -1;
    onpath = false;
    wpath = -1;
}  

//get Direction info for anim
if(onpath){
    dir = direction;
}
 

kburkhart84

Firehammer Games
I'm not sure what is going on in your case...but I can say that there is a tutorial made by @Nocturne about exactly that. I tried to google it and it seems that his site is frozen at the moment, or maybe it was moved.

I also found this information also by Nocturne about this topic.
 

JEMcG

Member
I'm not sure what is going on in your case...but I can say that there is a tutorial made by @Nocturne about exactly that. I tried to google it and it seems that his site is frozen at the moment, or maybe it was moved.

I also found this information also by Nocturne about this topic.
I gave this a read and it does sound pretty great.
I think I can edit the basic system they outline to work to my needs.
Replacing the built in pathfinding with a custom grid sounded scary, but the way they have outlined it sounds rather simple!

I appreciate the help, thanks!
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I've been summoned? 😊


That outlines how to use different techniques to have multiple enemies pathfind... It sounds to me that this is pretty much what you want, although you might want to also do some path point culling on the path to make it look more natuarl. CHeck out that article (it's for GMS1.4 but should still be valid for GMS2) and let me know how it goes... Once you have it working (if you use it!) then we can look at culling points to make "natural" paths. :)


EDIT: Just remembered! I did an updated version for GMS2 which you can get here: https://www.yoyogames.com/blog/459/dynamic-mp-grids
 

kburkhart84

Firehammer Games
That explains why the website where the tutorial used to be is no longer available...Nocturne had joined the Yoyo team and I'm assuming moved those tutorials bit by bit to yoyo's systems.
 

JEMcG

Member
I've been summoned? 😊


That outlines how to use different techniques to have multiple enemies pathfind... It sounds to me that this is pretty much what you want, although you might want to also do some path point culling on the path to make it look more natuarl. CHeck out that article (it's for GMS1.4 but should still be valid for GMS2) and let me know how it goes... Once you have it working (if you use it!) then we can look at culling points to make "natural" paths. :)


EDIT: Just remembered! I did an updated version for GMS2 which you can get here: https://www.yoyogames.com/blog/459/dynamic-mp-grids
Hi, thank you for your response.

I followed the GMS2 tutorial to see what info I could take from it, so thanks for that. I learnt some things!

In its current form I don't think it would solve my issues, but would make it more efficient, in terms of the size of the grid needed to avoid collisions etc.. so I’ll be working a version of it into the game for sure.

In your comment you mentioned more natural paths and I would love to hear how that would work.
I’m quite the novice in terms of gamemaker, but especially pathing, so I can’t come up with any idea of how that might work.

Culling the number of points certainly seems like it would fix my animation issues but how to do that without breaking the path entirely is a little beyond me at the moment, so I appreciate the help and would love to hear any ideas.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Okay, so, path culling is simply taking a path that's been created using the MP grid functions, and removing all the unnecessary points. This prevents horrible paths that go diagonal at Âș45 then start going straight at 0Âș to get to a destination that could be reached with single straight line at 32Âș (or whatever). So for this, you basically want to create a loop that goes through the each of the path points and checks to see if there is a collision between the current point and then next, and if there isn't then remove the next point and check again. When a collision is found, move to the next point and repeat the check until the end of the path. This way all the unnecessary points in the middle of the path are culled and only ones essential to actually moving around obstacles remain.
 

JEMcG

Member
Okay, so, path culling is simply taking a path that's been created using the MP grid functions, and removing all the unnecessary points. This prevents horrible paths that go diagonal at Âș45 then start going straight at 0Âș to get to a destination that could be reached with single straight line at 32Âș (or whatever). So for this, you basically want to create a loop that goes through the each of the path points and checks to see if there is a collision between the current point and then next, and if there isn't then remove the next point and check again. When a collision is found, move to the next point and repeat the check until the end of the path. This way all the unnecessary points in the middle of the path are culled and only ones essential to actually moving around obstacles remain.
Hi, thanks so much for continuing to help.

So I came up with something I was sure would work and of course it did not!


GML:
///@description Cull overlapping points from a path
///@arg pathname

var i = 1;
var pnum = path_get_number(argument0);

repeat(pnum-1){
    //Get location of the point starting at 1 (so the second point)
    var px = path_get_point_x(argument0, i);
    var py = path_get_point_y(argument0, i);
    //Get location of of the previous point
    var prx = path_get_point_x(argument0, i-1);
    var pry = path_get_point_y(argument0, i-1);
   
    //get the difference between the points
    var xdiff = abs(px - prx);
    var ydiff = abs(py - pry);
    if((xdiff <= 20) and (ydiff <= 20)){
        if(i == pnum){
            path_delete_point(argument0, i-1);
        } else {
            path_delete_point(argument0, i);
        }  
    }  
    i++
}
Unless I am mistaken there is no way to detect a collision between them in Gamemaker and so I resorted to just figuring out space between them.
Currently if I change the number I compare xdiff and ydiff to very little happens.
If I set it to a number less than 3 they clump right together and if I set it from anywhere from 3 upwards (I've tried as high as 3000) they clump less but at a fixed amount that appears to be too small.

I'm guessing it's either I don't know something or there's a big hole in my code, but if so I cannot see it.

For reference I am putting this between the generation of the path (or the changing of the path) and the path_start.
 

Yal

🐧 *penguin noises*
GMC Elder
Unless I am mistaken there is no way to detect a collision between them in Gamemaker and so I resorted to just figuring out space between them.
collision_line could be one such way (check for the wall objects directly rather than the mp_grid). Another approach could be to use mp_potential_step to approach one point at a time in order, and then switch to the next when you're near enough... looks more natural, and you spread the overhead out over the entire movement instead of having to do O(N^2) collision_line checks at the start (which can quickly get slow).
 
Top