Platformer Basics [Article]

Posted on: May 4, 2014 at 7:49 pm,

thumbnail_text2

Assumptions: This tutorial assumes you have basic knowledge of Game Maker: Studio. You should know how events and actions work at a very basic level, understand very simple GML code and that you know how to make sprites and objects and organize them in rooms.

Download the project files for this tutorial.

Making a platformer seems to be an incredibly common stumbling block in the career of many a budding game designer. They want to start ‘simple’ they tell themselves. So they task themselves with putting together a ‘basic’ platform game only to run headfirst into a lot of problems. Beginners find it hard to realize that a platform game is not an inherently ‘simple’ thing. And using the built in gravity and collision tools like solid can sometimes be flimsy and hard to work with. This tutorial hopes to show you how to put the basics of a platform engine together in pure GML code, without pulling your hair out and using only about 40 lines of code. Game Maker does make platformers easily, but it’s perhaps not as intuitively easy as many would guess.

 

The basic set up

So let’s start with the essentials. We’re going to need a room, two sprites and two objects. Name the sprites “spr_player” and “spr_wall” and the objects “obj_player” and “obj_wall“. Link the sprites to the objects and make sure to center the origin coordinates on your player and wall sprites.

s1

Now we have all of the pieces, how do we start to build the logic for our platformer? Well we need to start out by establishing a few variables (numbers) in our player object, that we can use to decide how quickly to move, how high to jump, how powerful gravity will be and so on. So go ahead and open up obj_player and add the create event. The only action we need for this event is the good old “Execute a piece of code” action found in the control tab of actions, under code. It’s this little icon: s2

Now you can insert the following code:

///Initialize Variables
grav = 0.2;
hsp = 0;
vsp = 0;
jumpspeed = 7;
movespeed = 4;

(Using three slashes for a comment instead of two changes the name of the action in the object editor which can be useful for tracking what the code does.)

Now instead of using ‘gravity’, ‘hspeed’ and ‘vspeed’ which are built in GM variables you can see we’ve opted to use our own made up variables for these effects called ‘grav’, ‘hsp’ and so on. This is because we’re going to implement gravity and movement by ourselves so that we keep complete control over what’s going on in the game. All we’re doing here is setting up some numbers for our code in the step event to use.

 

Player Inputs

Now that we have these variables ready, go ahead and add the step event to obj_player and create another ‘execute code’ action. This is where the rest of the code will go and is what will be executed by the player object on every single frame.

The first thing we need to do is get the player’s inputs at the start of the frame. This is to ask the question “What keys are currently being pressed?”.

//Get the player's input
key_right = keyboard_check(vk_right);
key_left = -keyboard_check(vk_left);
key_jump = keyboard_check_pressed(vk_space);

So we’re doing more of the same really. Filling up variables with numbers. “keyboard_check” will return a 1 or a 0 depending on if the key in brackets is being pressed. You can substitute vk_right, vk_left and so on with ord(“A”) and ord (“D”) or whatever other keyboard letters you want to use.

We do this in the step event because the state of our keys changes on every frame. So we need to know at the start of every frame what buttons are being pressed.

Key left is set up to return either -1 or 0 instead of 1 or 0 by putting a negative sign infront of keyboard_check. This is so that we can add key_right and key_left together and end up with either 1 for right, -1 for left, or 0 for neutral/both keys.

Which is exactly what we do next:

//React to inputs
move = key_left + key_right;
hsp = move * movespeed;
if (vsp < 10) vsp += grav;
 
if (place_meeting(x,y+1,obj_wall))
{
    vsp = key_jump * -jumpspeed
}

So now we know what buttons are being pressed, we can add our variables together to tell us which direction the player is trying to move on this frame. After the first line, the variable move now very conveniently contains either a 1, 0 or -1. We can now simply multiply this by our original movespeed variable which we set up in the create event to give us our intended horizontal speed for this frame. (So if we were holding left, key_left + key_right would equal -1. And then move would equal -1 multiplied by 4. Giving us a horizontal speed of -4)

Now we know how we’re planning to move horizontally we need to work out the same vertically. Now because we don’t want gravity to accelerate infinitely we’re going to check that our vertical speed on this frame isn’t already greater than 10 (which would indicate 10 pixels a second downwards speed). If our current vertical speed is LESS than ten, then we can continue to increase it by our grav variable. Meaning we steadily fall faster and faster each frame.

If we’re currently standing on the ground, then we want the ability to jump. So we use “if (place_meeting(x,y+1,obj_wall))” to check if there would be a collision with a wall object exactly one pixel below our player. If there is, then we can allow the player to jump. All we have to do is multiply key_jump (which will be 1 or 0) by our jumpspeed (negatively, because negative vertical speed moves us upwards.).

 

Collisions

So that was all the easy stuff. The thing that throws most people about making platform games is handling collisions. So here’s how to do it, pixel perfect, without ever using the ‘solid‘ functionality. Make sure your wall objects have solid turned off by the way, as that could cause you all sorts of problems later.

The idea is not as many people think, to detect a collision and then correct your position afterwards. The idea we’re using here is to detect the collision before it happens and adjust our movement accordingly.

Remember, up until this point we’ve only been filling variables with numbers, obj_player hasn’t actually moved anywhere. We’ve just worked out how much we would ideally like to move and we’ve put those numbers into hsp and vsp.

So, let’s handle one direction at a time. First of all, horizontal movement:

//Horizontal Collision
if (place_meeting(x+hsp,y,obj_wall))
{
    while(!place_meeting(x+sign(hsp),y,obj_wall))
    {
        x += sign(hsp);
    }
    hsp = 0;
}
x += hsp;

All we need to do is check whether or not there would be a collision if we were to move horizontally using our hsp variable. We do this with the line: if (place_meeting(x+hsp,y,obj_wall)). This checks, at the coordinates we would be about to move to (x + hsp, and our current y coordinate.) whether or not our sprite would collide with an instance of obj_wall. If we would, then it carries out the code contained in the curly {} braces.

Now we know there’s about to be a collision, we want to still move in that direction as much as we can without hitting the wall. Otherwise we’d stop with a gap between our player and the wall. So to do this, we use a while loop that moves us one pixel at a time towards the collision and stops just before we would hit it.

I should explain that the function “sign” returns 1 or -1 depending on whether the variable it is given is positive or negative. Meaning that if we are moving to the right, sign(hsp) should equal 1 and if we’re moving left it should equal -1. Also, the exclamation mark infront of the “place_meeting” check represents “not”. So instead of checking to see if the collision WOULD happen, we are looking to see if it would NOT happen.

Meaning we can translate the whole “while” line to mean: “While there is NOT a collision, one pixel in our direction of movement, with obj_wall

And while that condition happens to be true, we increase our x coordinate (actually moving the player object now) by 1. Until we’re right next to the wall, at which point the while condition is false, and the code will carry on as normal.

When we’re done moving as close as possible to the wall, we set our horizontal speed to be zero. As we’ve moved as much as we can without hitting the wall and we don’t want to move any further.

Now that we’ve handled any possible horizontal collisions we can safely commit to our movement and increase our x coordinate by whatever is left in hsp. If there was a collision, this will be zero (and we’ve already moved as much as possible.) If not, it’s whatever our inputs said it should be earlier in the code.

Now for vertical collision, we basically do exactly the same thing:

//Vertical Collision
if (place_meeting(x,y+vsp,obj_wall))
{
    while(!place_meeting(x,y+sign(vsp),obj_wall))
    {
        y += sign(vsp);
    }
    vsp = 0;
}
y += vsp;

As you can see the code is basically identical. We just replace hsp with vsp, and make our checks against our y coordinate as opposed to our x coordinate. Then once we’ve dealt with any collisions, move as before.

Once you’ve put all this together your step event should look like this:

//Get the player's input
key_right = keyboard_check(vk_right);
key_left = -keyboard_check(vk_left);
key_jump = keyboard_check_pressed(vk_space);
 
//React to inputs
move = key_left + key_right;
hsp = move * movespeed;
if (vsp < 10) vsp += grav;
 
if (place_meeting(x,y+1,obj_wall))
{
    vsp = key_jump * -jumpspeed
}
 
//Horizontal Collision
if (place_meeting(x+hsp,y,obj_wall))
{
    while(!place_meeting(x+sign(hsp),y,obj_wall))
    {
        x += sign(hsp);
    }
    hsp = 0;
}
x += hsp;
 
//Vertical Collision
if (place_meeting(x,y+vsp,obj_wall))
{
    while(!place_meeting(x,y+sign(vsp),obj_wall))
    {
        y += sign(vsp);
    }
    vsp = 0;
}
y += vsp;

And there you have it. A basic platform engine, with pixel perfect collisions in roughly 40 lines of GML code. All while keeping full control over every movement the player makes and without having to use built in variables like solid and gravity.

This article was based on my video tutorial: Platformer Basics

Further reading on avoiding the use of ‘Solid’.