GML Slopes and Platformer Movement for Beginners

Bentley

Member
GM Version: GMS 1.4 / GM2
Target Platform: ALL
Download: N/A
Links: BrandonSacState@yahoo.com

Summary
This tutorial is mainly about climbing slopes using a simple approach. When we get to a slope, measure the slope and compare the slope to how high you can climb. In addition, there are platformer basics like acceleration, jumping, setting sprites, and so forth. An example of what I'm talking about is in the GIF below (Yoshi's Island sprite).

tutorial gif 4.gif

Tutorial
I drew a randomly shaped sprite, checked precise collision mask, and then assigned it to obj_level. All the terrain is obj_level and it is what I collision check against.

approach script - moves one value to another value
GML:
if (argument0 < argument1) return min(argument0 + argument2, argument1);
return max(argument0 - argument2, argument1);
obj_player

Create Event
GML:
hspd      = 0;
vspd      = 0;
hspd_max  = 3;
vspd_max  = 6;
slope_max = 8;    // Max climb distance
accel     = 0.2;
frict     = 0.2;
grav      = 0.4;

[Step Event]
GML:
var kright, kleft, kjump, xdir, onground;

kright   = keyboard_check(vk_right);
kleft    = keyboard_check(vk_left);
kjump    = keyboard_check_pressed(vk_up);
xdir     = keyboard_check(vk_right) - keyboard_check(vk_left);
onground = place_meeting(x, y + 1, o_level);

// Handle horizontal input
if (xdir != 0)
{
    if (onground) sprite_index = spr_player_run;
    image_xscale = xdir;
    hspd = approach(hspd, hspd_max * xdir, accel);
}
else
{
    if (onground) sprite_index = spr_player_idle;
    hspd = approach(hspd, 0, fric);
}

// Jump
if (kjump && onground)
{
    sprite_index = spr_player_jump;
    vspd = -vspd_max;
}

// Gravity
if (!onground)
{
    if (vspd > 0) sprite_index = spr_player_fall;
    vspd = approach(vspd, vspd_max, grav);
}

// Actually move
var dy;
repeat (abs(hspd)) // Horizontal movement
{
   if (onground) // Check for slopes if we are on the ground
   {
        if (place_meeting(x + sign(hspd), y, obj_level)) // Up slope
        {
            dy = up_slope();     // Measure the slope
            if (dy <= slope_max) // Climbable
            {
                x += sign(hspd);
                y -= dy;
                continue;
            }
            hspd = 0; // Unclimbable
            break;
        }
        if (!place_meeting(x + sign(hspd), y + 1, obj_level)) // Down slope (just like we did up slopes)
        {
            dy = down_slope();         
            if (dy <= slope_max)
            {
                x += sign(hspd);
                y += dy;
                continue;
            }
            x += sign(hspd); // Continue the loop rather than break the loop to keep momentum
            continue;
        }
        x += sign(hspd); // Flat
    }
    else // We're in the air so we don't check for slopes
    {
        if (!place_meeting(x + sign(hspd), y, obj_level)) x += sign(hspd);
        else
        {
            hspd = 0;
            break;
        }
    }
}
repeat (abs(vspd)) // Vertical movement
{
    if (!place_meeting(x, y + sign(vspd), obj_level)) y += sign(vspd);
    else
    {
        vspd = 0;
        break;
    }
}
Slope scripts
[up_slope]
GML:
/// @function up_slope()
/// @desc This script will tell us how high the slope is.
var dy = 0;
while (place_meeting(x + sign(hspd), y - dy, obj_level)) // While we are not at the top of the slope
{
    dy++;
    if (dy > slope_max) break; // Break if the slope is greater than we can climb
}
return dy;
[down_slope]
GML:
/// @funct down_slope()
/// @desc This script will tell us how low the slope is.
var dy = 0;
while (!place_meeting(x + sign(hspd), y + dy + 1, obj_level)) // While we are not at the bottom of the slope
{
    dy++;
    if (dy > slope_max) break; // Break if the slope is greater than we can climb
}
return dy;
In both scripts, "dy" will be the exact distance to climb or 1 greater. That number is fed into: if (dy <= slope_max), which determines whether we can climb.
 
Last edited:

Lonewolff

Member
I'm not seeing anything here about the 'destroyable' element.

All I am seeing is inefficient terrain collision.
 

Bentley

Member
I'm not seeing anything here about the 'destroyable' element.

All I am seeing is inefficient terrain collision.
Well, I left making the destroyable terrain out, as I just wanted to focus on crossing slopes in that type of game. But, yeah, I could add that in.

If you don't mind, how can I make the terrain collision more efficient? Thanks for the reply.
 

Lonewolff

Member
Makes the title a bit misleading then. People will come along and think 'awesome!' And then will be disappointed when they find this is all only about pinning a character to varied height ground.
 
Just edit the title to "traversing slopes of destroyed terrain. NOTE: To anyone who can't be bothered to read the summary, and gets their overly entitled hopes up from just reading the title alone - techniques for destroyed terrain not included." (Millenials, eh? apparently they've never been told that the devil is in the details, and are very fragile mentally)
:)
 

Bentley

Member
Just edit the title to "traversing slopes of destroyed terrain. NOTE: To anyone who can't be bothered to read the summary, and gets their overly entitled hopes up from just reading the title alone - techniques for destroyed terrain not included." (Millenials, eh? apparently they've never been told that the devil is in the details, and are very fragile mentally)
:)
Haha : )
 

Lonewolff

Member
NOTE: To anyone who can't be bothered to read the summary, and gets their overly entitled hopes up from just reading the title alone - techniques for destroyed terrain not included." (Millenials, eh? apparently they've never been told that the devil is in the details, and are very fragile mentally):)
ROFL.

I don't follow tutorials. I already know everything :D
 

GilM

Member
Great tutorial man! I learned a lot from it. I hope to see many more from you for sure. One thing you should correct though is where you put "climb_room_height". Remember that you defined this in the create event as climb_height. Still a very useful tutorial for those who never knew how to do slopes in their games.
 

Bentley

Member
Great tutorial man! I learned a lot from it. I hope to see many more from you for sure. One thing you should correct though is where you put "climb_room_height". Remember that you defined this in the create event as climb_height. Still a very useful tutorial for those who never knew how to do slopes in their games.
Hey GilM, thanks for the feedback, and thanks for pointing out that typo as well! I edited the tutorial recently and auto-correct must have changed climb_height to "climb_room_height". That would be an odd variable name lol. I'll change that now.
 
Last edited:

MadPropz101

Member
Hey, the only thing i have to apply precise collision checking to is the objects that you collide with right? Not the player object itself?
I really hope this is the case because i'm using Spine and i assign all my bounding boxes there, so currently my character has a somewhat rectangular bbox assigned in Spine.
 

Bentley

Member
Hey, the only thing i have to apply precise collision checking to is the objects that you collide with right? Not the player object itself?
I really hope this is the case because i'm using Spine and i assign all my bounding boxes there, so currently my character has a somewhat rectangular bbox assigned in Spine.
Yes, you only need to check precise for the level sprite. I can't speak for Spine b/c I've never used it, but you probably won't have any problems.
 
Ughhhh thank you so ****ing much I've been looking for good slopes for weeks I bought things from the market place that weren't as useful as this thanks. One thing when going up steep slopes the player goes up faster is there a way to make him go the same speed on any steepness of slope
 

Bentley

Member
One thing when going up steep slopes the player goes up faster is there a way to make him go the same speed on any steepness of slope
Edit: I posted some ideas below.
Also can I make this work with hspeed instand of hspd? I like hspeed because it handles friction smoother
I avoided the built in speed variables b/c I feel like I can control pixel by pixel movement better this way.
 
Last edited:
I'm trying to slow down the players hspd when he goes up slopes. I tried doing if climb_height <= 4 hspd = hspd
And
If climb_height <= 8
hspd = hspd/2

I figured that since climb_height determines how steep of a slope you can move across this would work. It didn't. So then I tried

If dy <= 4

If dy <= 8

But that didn't work either.

So what tells the player to move up a steep slope when it's really steep? What tells the player when it has reached it's climb_height limit
 

Misty

Member
Does this have sliding down slopes? Like with momentum? And can it collide also with blocks, ceilings, and walls?
 
This is the code that slows down my player. What happens is that the hspd -= 0.01; is being ignored because of the slope collision code

Maybe because the slopes are moving the player a pixel it doesn't calculate the decimal idk.


Code:
if key_left && !key_right
hspd -= 0.5
else if !key_left && key_right
hspd += 0.5
else if !key_left && !key_right
{
    if hspd > 0
    {
      
        hspd -= 0.01
    }
    if hspd < 0
    {
        
        hspd += 0.01
    }
}


if hspd > 3
   hspd = 3
    
if hspd < -3
   hspd = -3
 

Bentley

Member
So what tells the player to move up a steep slope when it's really steep? What tells the player when it has reached it's climb_height limit
"dy" is what tells you how big a slope is.

I think there's some confusion, so let me explain how the code works, then I'll offer some ideas for slowing down movement when climbing.

The repeat loop asks the following question "hspd" times:
Is climb_height greater than or equal to the column of pixels 1 pixel ahead of you (x + sign(hspd))?
sprite1.png
You're that blue dot. Let's say you have an hspd of 3 and a climb_height of 3. In 1 frame, you'll end at that red dot.
The "repeat (hspd)" loop runs 3 times (b/c hspd is 3).
The first slope is 3 pixels high. climb_height is 3, so you'll climb it.
The second slope is 3 pixels high from your new position. So you'll climb that.
The same for the third slope.
Now the repeat loop is over and you're at the red dot.
(This all happens in 1 frame.)

If you zoom in on a slope it's just a bunch of pixel columns next to each other. When you get to one of those columns, there's only two possibilities: climb or don't. So that's why you can't climb individual columns slower. Here are some ways to slow you down based on slope height.

1. Think of climb_height as "climb_power". Instead of climbing "climb_height" each iteration of the loop, you use a temporary variable called climb_power.

Set var climb_power equal to climb_height before the loop. Every time you climb a slope, decrease climb_power by "dy" (how much you just climbed). So the more you climb, the more you decrease climb_power, making it more difficult to climb the next slope in the next iteration.

2. You could measure all the columns hspd away. You'd have some sort of formula where you'd add up their height total and the greater that number, the less your hspd. Increase hspd for down slopes.
 
Last edited:
Is there a way to make the code work with the built in functions hspeed and vspeed because I need friction and it's the only way I can move on slopes smoothly. I have another script that handles slopes with hspeed and vspeed but it only handles up slopes but not down slopes. Can you make this code work with down slopes and steep slopes as well.
Code:
//Roll back movement
//This is necessary as GM may or may not have carried out "Solid" behaviour
x=xprevious
y=yprevious
//Check if we're on the ground
if not (jumping or place_free(x,y+1))
{
  //We're on the ground so check we haven't just walked off a cliff
    OnGround = true;
  if not place_free(x+hspeed,y+abs(hspeed)+1)
  {
 
      //Search for the ground level
      x+=hspeed;
      y+=abs(hspeed)   
   move_outside_solid(90,100+200*abs(hspeed))  //200 not 1000  //200 not 1000
      //Roughly set vspeed for the benefit of any AIs that may use it
      vspeed=(y-yprevious)
      //Check if we hit a wall.
      if not place_free(x,y)
      {
          x=xprevious
          y=yprevious
          if hspeed<>0
          {
            move_contact_solid(180*(hspeed<0),abs(hspeed))
          } 
          hspeed=0
          vspeed=0
        
      }
  }
  else
  {
    //Lookout!! Cliff!!
    vspeed=0
    //Just move out horizontally
   move_contact_solid(180*(hspeed<0),abs(hspeed))
    //jump=true
    
  } 
}
else
{
    //Freefall code
    //Move horizontally
    if hspeed<>0
    {
      move_contact_solid(180*(hspeed<0),abs(hspeed))
 //here
      if not (place_free(x+sign(hspeed),y) or place_free(x+sign(hspeed),y-2))
      {
        hspeed=0
      }
    }
    //Move vertically
   if vspeed<>0
    {
      OnGround = false;
      move_contact_solid(90+180*(vspeed>0),abs(vspeed))
      if not place_free(x,y+sign(vspeed))
      {
        if vspeed>0
        {
          //jump= 0
        }
        vspeed=0
      }
    }
}
 
Let me first explain how the code works. Then I'll offer some ideas for slowing down movement when ascending, and speeding up movement when descending.

"dy" is what tells you how big a slope is.

When you get to a big slope, you can't "climb that slope slower". The reason being, a slope is just one column of pixels, and you either climb that or you don't. (What you can do is look at multiple columns ahead of you, and change your hspd and or your climb_height as you climb successive slopes. I'll get into that more at the bottom of this post.)

The repeat loop does the following "hspd" times:
Is climb height is greater than or equal to the height of the pixel column 1 pixel ahead of you? Climb it.

Let me try to explain better:
View attachment 21031
You're that blue dot. You have an hspd of 3 and a climb_height of 3. In 1 frame, you'll end at the red dot.
The "repeat (hspd)" loop runs 3 times (b/c hspd is 3).
The first slope is 3 pixels high. climb_height is 3, so you'll climb it.
The second slope is 3 pixels high from your new position. So you'll climb that.
The same for the third slope.
Now the repeat loop is over and you're at the red dot.
As a reminder, this all happens in 1 frame.

So I can think of some ways to slow you down on an up slope and speed you up on a down slope:

1. Think of climb_height as "climb_power" or something like that. Instead of climbing "climb_height" every iteration of the repeat loop, you use a temporary variable called climb_power. You set climb_power to climb_height before the loop. Every time you climb a slope, you decrease climb_power by "dy" (which is how much you just climbed). In the up/down slope scripts, instead of using climb_height, send in "climb_power" as an argument, and check if you can climb the slope using that value. The result of doing this would slow you down based on the height of the slopes.

2. You could measure multiple columns of pixels ahead of you, those columns being the slopes within (x + hspd), and have some sort of formula where you add up the height of those in-range slopes, and the greater the total, the lesser your hspd. For down slopes, you do the same except increase hspd instead (as if you're sliding down).
How would I do this exactly when I tried my player just went through the slopes
 

Bentley

Member
How would I do this exactly when I tried my player just went through the slopes
I might add the things we're talking about into this tutorial. I'll message you if I do. But for now, let me post some code that may help:
Code:
var climb_power = climb_height; // Create a temporary variable called "climb_power" and set it equal to climb_height

repeat (abs(hspd))
{
    // Slope ahead
    if (place_meeting(x + sign(hspd), y, obj_level))
    {
        // Measure the slope ("dy" is its height)
        var dy = up_slope();
 
        // Check climb_power insead of climb_height.
        if (dy <= climb_power)
        {
            // climb_power decreases every slope.
            // The result being it's harder and harder to climb each iteration you climb a slope, based on the slopes' heights
            climb_power -= dy;
        }
    }
 
    // Rest of code here...
}
This should slow you down when climbing a bunch of high slopes, hspd away. Tinker with the variables to get feel you want.
 
Last edited:

Amon

Member
This is pretty cool. Thanks for sharing. Would I be right in assuming that the first post has all the code updated?

[edit] I got it all working. This is, again, very cool stuff. Thank you again for sharing.
 
Last edited:

Joe Ellis

Member
This looks good, and the code isn't inefficient, it looks like alot at first but most player code ends up being quite big, and it only runs once per step so its not gonna lag the game
 

Bentley

Member
This is pretty cool. Thanks for sharing. Would I be right in assuming that the first post has all the code updated?

[edit] I got it all working. This is, again, very cool stuff. Thank you again for sharing.
Yeah, I think I'm going to update the tutorial. I'm going to slow the player down when ascending and speed the player up when descending. How fast/slow hspd becomes will depend on the both the number and the height of the slopes within hspd range. I'll tag you when it's posted.
 
The slowdown while climbing slopes isn't working for me I don't know exactly where to add the code you have a comment that says rest of code here I'm not sure what youean by it
 

Bentley

Member
The slowdown while climbing slopes isn't working for me I don't know exactly where to add the code you have a comment that says rest of code here I'm not sure what youean by it
I actually left out an important line, I just put it back in. It's at the very bottom of my post. There's only a few changes.
 

Bentley

Member
Can you add a slow down for downslopes too?
Do the exact same thing for down slopes as you did for up slopes:
Code:
// Down slope
if (!place_meeting(x + sign(hspd), y, obj_level))
{
    // Check climb_remaining against the height of the slope
    if (climb_remaining >= slope_height)
    {
        // Climable...
        
        x += sign(hspd);   // 1 pixel forward
        y -= slope_height; // slope_height pixel's up
    
        // Decrease climb_remaining so it's harder to climb next iteration
        climb_remaining -= slope_height;
    }
}
 
Last edited:
I actually left out an important line, I just put it back in. It's at the very bottom of my post. There's only a few changes.
I tweaked the slopes around and figured out how to add friction by accounting for subpixel movement. I have a problem when the player jumps into the side of a slopes he stops moving for a second. I believe it's because the player is no longer grounded when it touched the side of a wall and when it touches the side of a pixel it thinks it's a wall and stops him. How would I allow the player to. Be grounded while hitting the side of walls. And what determines how steep a slopes is. I think you said dy. So how would I say if a slopes dy is 10 pixels high switch state to state.playerslide
 
How did you make the player in the gif gif flip left and right because when I use image_xscale = 1 and image_xscale = -1 to flip my Sprite it also flips the mask and it messes with the collisions even tho I have everything centered and it doesn't effect where the player is it still messes with it
 
And how do I make the player grounded even when he touches the side of a wall because he's only grounded when the floor is beneathe is feet
 

Bentley

Member
@Christopher Rosa
grounded script
Code:
return (place_meeting(x, y + 1, obj_level) || place_meeting(x + image_xscale, y, obj_level))
I'm using "image_xscale" as you said that's what your using and you centered your origin. So this should tell you if you're facing obj_level.

You're probably going to have problems considering yourself on ground this way. For example, if your falling but there's a solid 1 pixel in front of you, you'll be considered standing. If you use this script to see whether you can jump, you'll be able to jump from mid-air. But maybe you're using the script in a different way. Just something to keep in mind.
 
Last edited:
@Christopher Rosa
grounded script
Code:
return (place_meeting(x, y + 1, obj_level) || place_meeting(x + image_xscale, y, obj_level))
I'm using "image_xscale" as you said that's what your using and you centered your origin. So this should tell you if you're facing obj_level.

You're probably going to have problems considering yourself on ground this way. For example, if your falling but there's a solid 1 pixel in front of you, you'll be considered standing. If you use this script to see whether you can jump, you'll be able to jump from mid-air. But maybe you're using the script in a different way. Just something to keep in mind.
I worked for making the player grounded when touching the side of a wall but the glitch I'm having is when I change the image xscale of my player it makes his fall off the slope when going down slope. When I go up a slope then hit right I fall right off
 
So this is like climbing up stairs rather than going up and down slopes. So if dy = 9 that means you climb up 9 pixels at once which is bad. It teleports up 9 pixels or down 9 pixels instead of going 1 pixel at a time like how it does horizontally. I found a 360 slope engine that uses dcos and dsin and it goes around slopes smoothly but I don't understand how it works.
 

Bentley

Member
Climbing stairs is a good way to put it. Climbing stairs that get harder and harder with ever stair you climb.

What do you want to happen when there's a wall x + 1 away.
 
Last edited:
Climbing stairs is a good way to put it. Climbing stairs that get harder and harder with ever stair you climb.

What do you want to happen when there's a wall x + 1 away.


Ive been trying to make going up slopes act the same as walking on a flat ground. Soo when dy = 9 the player still travels the 9 pixels 1 pixel at a time or the speed of the player so it doesn't just teleport 9 pixels up. If you know what I mean? When you're on flat ground if the floor is 30 pixels in width the player doesn't teleport 30 pixels to the right or left it goes one pixel at a time or what the speed is. That's how I want it when going up pixels going up the slope the same as flat ground.
 

Bentley

Member
Where we are getting confused is the way we are thinking about slopes. That is my bad. Do not even think about slopes for a second. Let's call them columns.

A column has a width of just one pixel.
The code looks ahead at the next column and measures how many pixels are on top of that column.

Now imagine climbing that column halfway. It does not really make sense. That is what I meant when I said, "You either climb it or you don't".

I think moving is synonymous with what you are calling teleporting. x + 5 moves you instantly to x + 5. It does not move you pixel by pixel.

Maybe a climb_angle is better, and you measure the angle and determine whether you can climb based on that.

There are undoubtedly better ways to climb slopes. This is just a simple way that works for me.
 
Last edited:
Where we are getting confused is the way we are thinking about slopes. That is my bad. Do not even think about slopes for a second. Let's call them columns.
What better ways do you know of for doing this because I'd like to achieve having the player slide off hills and ramps really smooth
A column has a width of just one pixel.
The code looks ahead at the next column and measures how many pixels are on top of that column.

Now imagine climbing that column halfway. It does not really make sense. That is what I meant when I said, "You either climb it or you don't".

I think moving is synonymous with what you are calling teleporting. x + 5 moves you instantly to x + 5. It does not move you pixel by pixel.

Maybe a climb_angle is better, and you measure the angle and determine whether you can climb based on that.

There are undoubtedly better ways to climb slopes. This is just a simple way that works for me.
 

Bentley

Member
Any wall support? When you draw a straight vertical line on the terrain, the player ends up getting stuck.
If by "wall support" you mean that you want to place other objects that you can collide with, the simplest solution would be to make them a child of obj_terrain. That way, a collision check for obj_terrain includes a collision check for obj_wall (or w/e you name the new object).

If you mean that when you are drawing the sprite for obj_terrain, you draw a vertical line, the player should be stopped if the vertical line is higher than his climb_max variable. If you want your player to be able climb higher, just increase climb_max.
 
Last edited:
Makes the title a bit misleading then. People will come along and think 'awesome!' And then will be disappointed when they find this is all only about pinning a character to varied height ground.

is there other things out there to do slopes. i want smooth slopes ramps ect i dont want to be pinned to varied height ground.
 

Bentley

Member
Makes the title a bit misleading then. People will come along and think 'awesome!' And then will be disappointed when they find this is all only about pinning a character to varied height ground.

is there other things out there to do slopes. i want smooth slopes ramps ect i dont want to be pinned to varied height ground.

You want it where if your hspd is 5 and there is a slope, your hspd is changed to account for the slope's angle?

That sounds like a good way to go about it. You would need to get the angle of the slope ahead. You would then need to use lengthdir_x and y and plug in hspd as the len argument.

By the way, if your slopes are not changing (you don't have destroyable terrain) you can just have a couple place meeting checks:
repeat (abs_hspd))
{
if (place_meeting(x + sign(hspd), y, obj_wall) && !place_meeting(x + sign(hspd), y - 1, obj_wall))
{
x += sign(hspd);
y--;
}
}
You can do the same for checking 2 up.
 
Last edited:
Top