Generic Platformer

GM Version: GameMaker: Studio
Target Platform: All
Downloads: Part 1 GMX, Part 2 GMX

Summary:
This tutorial covers a very simple way to create a platformer using Game Maker. There are several parts I will be covering going from basic to more advanced though each part can be considered its own platformer engine.

Tutorial:

Part 1:
This is a very generic platformer with slopes. The basic idea behind the movement in these platformers is the player first tries to move directly to where it is supposed to go, if that fails, we need to correct the problem.

There are many built in variables included with instances that automatically handle movement and collisions. Here is a list. The main variables that I use will be hspeed, vspeed, and gravity for the player. The walls will use a variable called solid.

hspeed adds to the x value of an instance every step. vspeed adds to the y value every step. gravity adds to the vspeed every step (assuming you don't change gravity_direction). If you set a collision event with an object that is solid and that event is triggered, the instance will move back to where it was before it moved in the step event.

So now we have the background down. I'll just post all the code for the player object:
Player Step Event
Code:
///standard platform movement

//controls for movement
hkey = keyboard_check( vk_right ) - keyboard_check( vk_left );

//check if on ground
if( place_free( x, y+1 )){
    gravity = 0.8; //set gravity - we are in the air!
} else {
    gravity = 0; //no need for gravity on ground

    //we are on ground so we can check if we need to jump
    if( keyboard_check_pressed( vk_up )){
        vspeed = -12;
    }
}

//set horizontal movement based on controls
if( hkey == 0 ){
    hspeed *= 0.75; //friction
    if( abs( hspeed ) < 0.5 ){ hspeed = 0; }
} else {
    hspeed *= 0.75;
    hspeed += 2 * sign( hkey );
}
Player End Step Event
Code:
///Collision Correction

//Moving into a solid puts us back as if we never moved.
//This part allows us to move on slopes and fill the gaps.

//First check if we need to correct for collision
if( speed != 0 and x == xprevious and y == yprevious ){

    //move up first if vspeed is up
    if( vspeed < 0 ){
        move_contact_solid( 90, -vspeed );
    }

    //move over if hspeed is not 0
    if( hspeed != 0 ){
        //setting some temporary vars
        var temp_y = y;
        //move up
        move_contact_solid( 90, abs( hspeed ));
        //move over
        move_contact_solid( 90 - sign( hspeed ) * 90, abs( hspeed ));
        //move down what we moved up
        if(temp_y-y != 0){
            move_contact_solid( 270, temp_y-y);
        }
        //move down again if it puts us on ground
        if( !place_free( x, y + abs( hspeed ) + 1 )){
            move_contact_solid( 270, abs( hspeed ));
        }
    }
    //move down if vspeed is down
    if( vspeed > 0 ){
        move_contact_solid( 270, vspeed );
    }
    //if we are blocked then set vspeed to 0
    if( !place_free( x, y + sign( vspeed ))){
        vspeed = 0;
    }
    //set hspeed to 0 if we can't move horizontally (we're blocked)
    if( hspeed != 0 and x == xprevious ){
        hspeed = 0;
    }
}
Player Collision with Wall Event
Code:
///empty script to trigger collision event

//we don't need to do anything!
So as you can see, the code is heavily commented. I'll tell you what's going on any way. In the step event, we get the player's input and use that to set hspeed and vspeed. gravity happens when we aren't on ground and stops when we are. We can jump when we are on the ground. Going back to before, if we can't move to ( x + hspeed , y + vspeed ) without colliding with a solid, we stay at ( x , y ). That's where the end step event "corrects" this problem.

With the end step, the magic kicks in. We are moving up, up, over, down, possibly down, and down again. This method is the way it is because logically it makes sense. Players typically are trying to jump on top of platforms and are rarely trying to jump under a platform (see Figure A). If we're jumping, we want to move up first. To handle slopes, we move up over down and possibly down to stick to downward slopes. We have to move like this because slopes aren't the illusion we try to sell. They aren't actually flat. With square pixels, slopes are more like stairs (see Figure B). Then if we are falling, we will move down last for that. Finally we set our speeds to 0 when we are blocked. This makes the controls snappy and feel more natural.


Figure A


Figure B

There isn't any code for the wall objects. They just need to exist and be marked solid.

That's really all there is to it. Next part I will cover jump through platforms and slopes. :)

Here is a GMX of part 1. It has everything covered in this first part.
 
Last edited:
Apologies in advance for double post. Limit of 10000 characters per post. Still awaiting moderation so if this is a problem, please let me know. Thanks!

Part 2:
This next part, we will add some more code to allow jump through platforms. The jump through platforms can be slopes! This is because of how we implemented movement in part 1.

We will be adding some additional variables to our wall objects though. Easiest way to handle all the wall objects is to use parenting. I have a standard non-jump through wall object as the main parent. Any other non-jump though wall objects, like slopes, are children of that main wall object. I also have a standard jump through wall object with children. This standard jump through wall object uses the main wall object as a parent. Now for the code:

Create Event Wall Object
Code:
jumpThru = false;
Create Event Jump Through Object
Code:
jumpThru = true;
slope = 0; //0 for no slope
Create Event Jump Through Slope Object
Code:
jumpThru = true;
slope = -1; //-1 if slope surface normal points left and 1 for right
Not a whole lot there but it gets the job done.

Now to make everything optimized (since we are pros that think ahead), I created a couple scripts to give us a list of only the wall objects that we need to worry about.

move_get_collision_list(obj) - Returns a list of obj instances that collide with the player's axis aligned bounding box covering the whole area of movement. This makes it so we don't have to consider every single wall object comparing positions and setting/resetting variables several times a step.
Code:
///move_get_collision_list(obj)

var aabbLeft = min(bbox_left,bbox_left + hspeed);
var aabbRight = max(bbox_right,bbox_right + hspeed);
var aabbTop = min(bbox_top,bbox_top + vspeed) - abs(hspeed);
var aabbBottom = max(bbox_bottom,bbox_bottom + vspeed) + abs(hspeed);
var _list = ds_list_create();
var retId;

with(argument0){
    if(id != other.id){
        retId = collision_rectangle(aabbLeft,aabbTop,aabbRight,aabbBottom,id,false,false);
        if(retId != noone){ ds_list_add(_list,retId); }
    }
}

return _list;
area_get_collision_list(obj) - Gets a list of obj instances that collide with the player's aabb. The area is expanded a little below the player to get instances we are standing on. We need this to properly determine if we are on ground or falling through a jump through platform.
Code:
///area_get_collision_list(obj)

var _list = ds_list_create();
var retId;

with(argument0){
    if(id != other.id){
        retId = collision_rectangle(bbox_left,bbox_top,bbox_right,bbox_bottom+2,id,false,false);
        if(retId != noone){ ds_list_add(_list,retId); }
    }
}

return _list;
update_solid(list,direction[,drop]) - This is a huge part of making the jump through platforms work. We grab our list, created from the above scripts, and based on the direction the player is moving, we follow a specific set of rules. Moving up, we shouldn't get blocked by any jump through platforms. Moving down we should be blocked only by platforms below the player and not colliding with the player. Moving horizontally is the most complex case. We only collide with slopes that have a normal pointing against the direction we are moving. Also can't be colliding and we should be above them. Now the optional drop argument is either true or false. True means we are dropping through all jump through platforms. False or not specifying means normal behavior. You can set this value to be some kind of input for dropping down or even more creative falling down from an enemy attack?
Code:
///update_solid(list,direction[,drop])

var _list = argument[0];
var _dir = (round(argument[1]/90)*90) mod 360;
if( _dir < 0 ){ _dir += 360; }
var _i, objID, slope;
if(argument_count>2){
    if(argument[2]){ _dir = 90; }
}

if( _dir == 90 ){

    for( _i = 0; _i < ds_list_size(_list); _i++ ){
      
        objID = ds_list_find_value(_list,_i);
        if(!objID.jumpThru){ continue; }
        objID.solid = false;
    }
} else {

if( _dir == 270 ){

    for( _i = 0; _i < ds_list_size(_list); _i++ ){
      
        objID = ds_list_find_value(_list,_i);
        if(!objID.jumpThru){ continue; }
        if(bbox_bottom > objID.bbox_bottom ){
            objID.solid = false;
            continue;
        }
        if(place_meeting(x,y,objID)){
            objID.solid = false;
            continue;
        }
      
        objID.solid = true;
    }
} else {

    for( _i = 0; _i < ds_list_size(_list); _i++ ){
      
        objID = ds_list_find_value(_list,_i);
        if(!objID.jumpThru){ continue; }
        slope = sign(objID.slope*objID.image_xscale);
        if(slope == 0){
            objID.solid = false;
            continue;
        }
        if(bbox_bottom > objID.bbox_bottom ){
            objID.solid = false;
            continue;
        }
        if(place_meeting(x,y,objID)){
            objID.solid = false;
            continue;
        }
        if(_dir == 0){
            if(slope == 1){
                objID.solid = false;
                continue;
            }
            if(bbox_left > objID.bbox_right){
                objID.solid = false;
                continue;
            }
          
            objID.solid = true;
          
        } else {
            if(slope == -1){
                objID.solid = false;
                continue;
            }
            if(bbox_right < objID.bbox_left){
                objID.solid = false;
                continue;
            }
          
            objID.solid = true;
          
        }
    }
}}
reset_solid(list) - This makes all jump through platforms in the list solid again. This is important to run before the list is destroyed. Missing it could cause the player to randomly fall through platforms so don't forget it.
Code:
///reset_solid(list)

var _list = argument0;
var _i, objID;

for( _i = 0; _i < ds_list_size(_list); _i++ ){
  
    objID = ds_list_find_value(_list,_i);
    if(!objID.jumpThru){ continue; }
    objID.solid = true;
}

So that's all the new scripts for this part. There are changes to the player code:
Player Step Event
Code:
///standard platform movement

//controls for movement
hkey = keyboard_check( vk_right ) - keyboard_check( vk_left );

//area list to get accurate on ground readings and stuff
var colList = area_get_collision_list(*ADD WALL OBJECT HERE*);

//update jumpThru state
update_solid(colList,270,keyboard_check(vk_down));

//check if on ground
if( place_free( x, y+1 )){
    gravity = 0.8; //set gravity - we are in the air!
} else {
    gravity = 0; //no need for gravity on ground

    //we are on ground so we can check if we need to jump
    if( keyboard_check_pressed( vk_up )){
        vspeed = -12;
    }
}

//set horizontal movement based on controls
if( hkey == 0 ){
    hspeed *= 0.75; //friction
    if( abs( hspeed ) < 0.5 ){ hspeed = 0; }
} else {
    hspeed *= 0.75;
    hspeed += 2 * sign( hkey );
}

//reset jumpThru states
reset_solid(colList);

//clean up
ds_list_destroy(colList);
Player End Step Event
Code:
///Collision Correction

//Moving into a solid puts us back as if we never moved.
//This part allows us to move on slopes and fill the gaps.

//First check if we need to correct for collision
if( speed != 0 and x == xprevious and y == yprevious ){
   
    //get a list of all possible wall objects to collide with
    var colList = move_get_collision_list(*ADD WALL OBJECT HERE*);

    //move up first if vspeed is up
    if( vspeed < 0 ){
        //update jumpThru state
        update_solid(colList,90,keyboard_check(vk_down));
        move_contact_solid( 90, -vspeed );
    }
   
    //move over if hspeed is not 0
    if( hspeed != 0 ){
        //setting some temporary vars
        var temp_y = y;
        //update jumpThru state
        update_solid(colList,90,keyboard_check(vk_down));
        //move up
        move_contact_solid( 90, abs( hspeed ));

        update_solid(colList,90 - sign( hspeed ) * 90,keyboard_check(vk_down));
        //move over
        move_contact_solid( 90 - sign( hspeed ) * 90, abs( hspeed ));

        update_solid(colList,270,keyboard_check(vk_down));
        //move down what we moved up
        if(temp_y-y != 0){
            move_contact_solid( 270, temp_y-y);
        }
        //move down again if it puts us on ground
        if( !place_free( x, y + abs( hspeed ) + 1 )){
            move_contact_solid( 270, abs( hspeed ));
        }
    }
    //move down if vspeed is down
    if( vspeed > 0 ){
        update_solid(colList,270,keyboard_check(vk_down));
        move_contact_solid( 270, vspeed );
    }
    //if we are blocked then set vspeed to 0
    update_solid(colList,180+90*sign(vspeed),keyboard_check(vk_down));
    if( !place_free( x, y + sign( vspeed ))){
        vspeed = 0;
    }
    //set hspeed to 0 if we can't move horizontally (we're blocked)
    if( hspeed != 0 and x == xprevious ){
        hspeed = 0;
    }
   
    //reset jumpThru states
    reset_solid(colList);
   
    //clean up
    ds_list_destroy(colList);
}

Basically what the new code does is grab a list of wall objects that may get in the way. Using that list, we change the wall objects solid variable depending on the players current movement (up, down or over) and position. Then we reset solid to true and clean up our list. Note that this works with multiple objects like your player and enemies.

That concludes part 2. I've got a few more ideas but not sure which for the next part. I'll gladly take suggestions or requests.

Here is the GMX for part 2. Everything covered here is included.
 
Last edited:

chance

predictably random
Forum Staff
Moderator
I've only looked closely at the .gmx in Part 1 so far. But it looks fine. Good style. Proper syntax. And well commented. I appreciate that you took the time to provide a download, even though the tutorials themselves are contained in the posts. I think this makes them more accessible to beginners, so I wish everyone did this.

There are many different approaches and techniques for platform movement, although most of them share a few common features. This approach has many of the most commonly used features. And it's well written and explained nicely with comments. Good addition to the Tutorials forum.
 
Top