[SOLVED] Detect Terrain Beneath Player

Storm1208

Member
Hey all! I'm working on a 2D platforming infinite runner and there's a feature in it where after jumping, the player must press the space bar with perfect timing before hitting the ground. If they mess up, it causes them to trip and slow down.

My issue is that I can't seem to figure out how to gauge the distance between the player and the ground to check if the button timing was correct. Here's what my code looks like so far:
GML:
if(space_key && vsp > 0 && (obj_ground.y - y < 80 && obj_ground.y - y > 5)) //if the space key is pressed, the player is falling, and they are within 5-80 pixels from the ground
        {
            //good case   
            show_debug_message("Good Landing!");
            var onepixel = sign(vsp);
            while(!place_meeting(x, y + onepixel, obj_ground))
            {
                y = y + onepixel;   
            }
            vsp = 0;
            my_state = player_state.on_ground;
        }
        else
        {
            if(vsp > 0 && obj_ground.y - y < 5) //else, the player was too close to the ground (I should probably check for space here too but it may not be necessary)
            {
                //bad case
                show_debug_message("Bad Landing!");
                var onepixel = sign(vsp);
                while(!place_meeting(x, y + onepixel, obj_ground))
                {
                    y = y + onepixel;   
                }
                vsp = 0;
                my_state = player_state.on_ground;
            }
        }
This current implementation isn't working. I also previously tried using GMLs "collision_line" function like so: collision_line(x, y, x, y + 400, obj_ground, false, true); but it wasn't working either. The game will eventually have more terrain than just "obj_ground" (I'm planning on adding sloped ground and grinding rails too) so another flaw in this system is it only detects for one type of ground object (obj_ground) even though I'm going to add more later.

If anyone has experience with ground detection like this I'd love some help!
 

Lady Glitch

Member
I'd use collision_rectangle_list(). Here, this should work:
Note: (Make obj_ground a parent of all of your ground objects - so it will work with all of them.)

GML:
if (space_key && vsp > 0)
{
    var minspace = 5; // min distance from the ground to make a "good landing"
    var maxspace = 80; // max distance from the ground to make a "good landing"
    var groundlist = ds_list_create(); // create a list to store all of the ground instances
    groundlist = collision_rectangle_list(bbox_left, bbox_bottom, bbox_right, bbox_bottom+maxspace, obj_ground, false, true, groundlist, false); // check for ground instances below the player

    if ds_list_size(groundlist) > 0 // if any ground instances are found - we do this
    {
        var nearestground = -1; // closest ground instance id
        var nearestground_dist = 1000000; // distance to closest ground instance
        var checkground = -1; // id of the ground instance we currently check
        var checkground_dist = 1000000; // distance of the ground instance we currently check

        /// loop thru the list to find the nearest ground instance
        for (var i = 0; i < ds_list_size(groundlist); i++)
        {
            checkground = ds_list_find_value(groundlist, I);
            checkground_dist = (checkground.bbox_top - bbox_bottom);
            if (checkground_dist < nearestground_dist)
            {
                nearestground_dist = checkground_dist;
                nearestground = checkground;
            }
        }

        /// decide if it's a good of a bad landing
        if nearestground_dist <= minspace
        {
            //bad case
            show_debug_message("Bad Landing!");
        } else {
            //good case
            show_debug_message("Good Landing!");
        }

        /// I use bboxes here to move the player, but you could use your stuff
        y = nearestground.bbox_top -(bbox_bottom-y) -1;
        vsp = 0;
        my_state = player_state.on_ground;
    }
    ds_list_destroy(groundlist); // destroy the list
}
Edit: formatting stuff and typos
 
Last edited:

TheouAegis

Member
Rather than checking if there is ground N steps away, check how long it has been since the player pressed the required button. For example, you could set an alarm or a timer to 4 when the player presses the button. When he lands, check if the timer is greater than 0 and if it is let the player make a safe landing.
 

Storm1208

Member
Rather than checking if there is ground N steps away, check how long it has been since the player pressed the required button. For example, you could set an alarm or a timer to 4 when the player presses the button. When he lands, check if the timer is greater than 0 and if it is let the player make a safe landing.
For this approach, how would the initial value of the timer be set? For example if the player jumped the exact same way every time (say there was 4 frames of air time like in your example) it would be fine, but if the player jumped differently it might not work anymore. Say they jump right as a downward slope was coming up, then they would fall way longer and the timer value being used from before would be too short.
 

Storm1208

Member
I'd use collision_rectangle_list(). Here, this should work:
Note: (Make obj_ground a parent of all of your ground objects - so it will work with all of them.)

GML:
if (space_key && vsp > 0)
{
    var minspace = 5; // min distance from the ground to make a "good landing"
    var maxspace = 80; // max distance from the ground to make a "good landing"
    var groundlist = ds_list_create(); // create a list to store all of the ground instances
    groundlist = collision_rectangle_list(bbox_left, bbox_bottom, bbox_right, bbox_bottom+maxspace, obj_ground, false, true, groundlist, false); // check for ground instances below the player

    if ds_list_size(groundlist) > 0 // if any ground instances are found - we do this
    {
        var nearestground = -1; // closest ground instance id
        var nearestground_dist = 1000000; // distance to closest ground instance
        var checkground = -1; // id of the ground instance we currently check
        var checkground_dist = 1000000; // distance of the ground instance we currently check

        /// loop thru the list to find the nearest ground instance
        for (var i = 0; i < ds_list_size(groundlist); i++)
        {
            checkground = ds_list_find_value(groundlist, I);
            checkground_dist = (checkground.bbox_top - bbox_bottom);
            if (checkground_dist < nearestground_dist)
            {
                nearestground_dist = checkground_dist;
                nearestground = checkground;
            }
        }

        /// decide if it's a good of a bad landing
        if nearestground_dist <= minspace
        {
            //bad case
            show_debug_message("Bad Landing!");
        } else {
            //good case
            show_debug_message("Good Landing!");
        }

        /// I use bboxes here to move the player, but you could use your stuff
        y = nearestground.bbox_top -(bbox_bottom-y) -1;
        vsp = 0;
        my_state = player_state.on_ground;
    }
    ds_list_destroy(groundlist); // destroy the list
}
Edit: formatting stuff and typos
Oh, this is actually my first time hearing of "collision_rectangle_list()" so I'll def check out the documentation for it!

Also thanks for the note on parenting. That should address my issue with having different ground types!
 

TheouAegis

Member
Player needs to press the button within four steps, or whatever, of colliding with the ground, right? So if the player jumps over a downward slope and presses the button too early, it's going to count down before he touches the ground, which means he will stumble, right? He didn't press it at the right time, he pressed it too early.

Or are you talking about a different type of mechanic? The typical jump recovery mechanic is you press a button within, say, 2 to 4 steps of touching the ground. The only other common mechanic that I can think of is having to press the button at a certain peak point in the jump, which itself would just be simply setting a variable to true if the player succeeds at that. But I am pretty sure you are asking about the former, which is classically just a timer check.

You may also want to make sure the vertical speed is actually greater than 1, depending on how you handle slopes or any other factors to consider.
 

Storm1208

Member
Player needs to press the button within four steps, or whatever, of colliding with the ground, right? So if the player jumps over a downward slope and presses the button too early, it's going to count down before he touches the ground, which means he will stumble, right? He didn't press it at the right time, he pressed it too early.

Or are you talking about a different type of mechanic? The typical jump recovery mechanic is you press a button within, say, 2 to 4 steps of touching the ground. The only other common mechanic that I can think of is having to press the button at a certain peak point in the jump, which itself would just be simply setting a variable to true if the player succeeds at that. But I am pretty sure you are asking about the former, which is classically just a timer check.

You may also want to make sure the vertical speed is actually greater than 1, depending on how you handle slopes or any other factors to consider.
AH! I see what you're saying now! Setting up a timer that correlates with the spacebar press like that should work, thank you!
 
Top