Basic Platformer Mechanics in GameMaker: Studio

I’m fond of platformers. It’s only natural that I spent the past year studying and refining platformer engines for GameMaker: Studio. Here’s what I actually use for my engine.

Fuzeboy early gameplay

The following code is adapted from Zack Bell’s platformer code (from his article Understanding Collisions pt.2). Once you grasp the basics, you can edit the code to adapt to your specific needs. That’s what I’ve dove.

UPDATE: Please take a look at this other, optimized code. I’ve rewritten it and make some speed tests. It’s 2.8x more efficient than the code below. Enjoy!

Don’t use GameMaker’s internal variables.

For speeds (vertical and horizontal) I never use the internal variables (e.g. hspeed, vspeed, speeddirection, gravity and so on); there’s only so much control you can have over those variables and how they interact with your objects… sooner or later you’ll find yourself with no idea of what’s going on in your game.

I prefer to directly manipulate the X and Y coordinates of my objects, so I usually implement my own speed variables.

Platformer Initialization Script

The following script is executed once, in the create event by those objects that need to move. Everything that moves (e.g. in my game that would be the Player, the broken pieces of pots and crate boxes, bombs, collectible items…) must initialize some variables that will be used in lieu of GM internal ones.

This is a stripped down version of what I’m using since I’ve customized mine heavily (with sprite management stuff we don’t need for movement)

///scr_platformer_init()

// This script is called in the create event of moving objects

// Initialize the variables used for movement code
xVel = 0            // X Velocity
yVel = 0            // Y Velocity

xVelSub = 0         // X Sub-pixel movement
yVelSub = 0         // Y Sub-pixel movement

// Previous X/Y coords to use as we see fit
xprev   = 0         // Previous X ausiliary var
yprev   = 0         // Previous Y ausiliary var

We might need to use our own variables for the previous X and Y positions as well. That’s why I define my own xprev and yprev as well.

Movement Code

This is the actual movement code that must be placed in the step event after you have modified the xVel and yVel as you see fit for you objects (like player input, jumps, falling mechanics and so on)

///scr_move()

xprev = x
yprev = y

// Handle sub-pixel movement
xVelSub += xVel
yVelSub += yVel

var xVelNew = round(xVelSub);
var yVelNew = round(yVelSub);

xVelSub -= xVelNew
yVelSub -= yVelNew
  
// Speed Directions
var xdir = sign(xVelNew)
var ydir = sign(yVelNew)

// Vertical Movement
repeat(abs(yVelNew))
{
    if place_meeting(x, y + ydir, obj_solid)
    {
        yVel = 0
        break
    }    
    else
    {
        y += ydir
    }
}

// Horizontal Movement
repeat(abs(xVelNew))
{
    if place_meeting(x + xdir, y, obj_solid)
    {
        xVel = 0
        break
    }
    else
    {
        x += xdir
    }
}

As you can see this looks terribly similar to Zack Bell’s code. What I use now is quite customized in the collision check part. I’m not using a single place_meeting anymore. Instead I use multiple line_collision with different objects based on the direction I’m actually headed.

My code looks more like this one, indeed…

///scr_move()

xprev = x
yprev = y

// Handle sub-pixel movement
xVelSub += xVel
yVelSub += yVel

var xVelNew = round(xVelSub);
var yVelNew = round(yVelSub);

xVelSub -= xVelNew
yVelSub -= yVelNew

if ((xVelNew == 0) && (yVelNew == 0))
    exit
    
// Speed Directions
var xdir = sign(xVelNew)
var ydir = sign(yVelNew)

// Vertical Movement
repeat(abs(yVelNew))
{
    if ydir == 1
        var coll    = is_on_ground()
    else
        var coll    = collision_line(bbox_left, bbox_top - 1, bbox_right, bbox_top - 1, obj_solid, false, true)

    if  coll
    {
        yVel = 0
        break
    }    
    else
    {
        y += ydir
    }
}

// Horizontal Movement
repeat(abs(xVelNew))
{
    if  collision_rectangle(bbox_left + xdir, bbox_bottom, bbox_right + xdir, bbox_top, obj_solid, false, true) ||
        collision_rectangle(bbox_left + xdir, bbox_bottom, bbox_right + xdir, bbox_top, obj_crate_solid, false, true)
    {
        xVel = 0
        break
    }
    else
    {
        x += xdir
    }
}

And the is_on_ground script looks like this one

///is_on_ground()

// Check For Solid Ground
var on_ground               = collision_line(bbox_left, bbox_bottom + 1, bbox_right, bbox_bottom + 1, obj_solid, false, true)
if on_ground
    return on_ground

// Check for Crate Object
var on_crate                = collision_line(bbox_left, bbox_bottom + 1, bbox_right, bbox_bottom + 1, obj_crate_solid, false, true)
if on_crate
    return on_crate
 
// Check For One Way Platform      
var on_one_way_platforms    = collision_line(bbox_left, bbox_bottom + 1, bbox_right, bbox_bottom + 1, obj_one_way_platform, false, true)
if on_one_way_platforms
    return on_one_way_platforms

// Check for Moving Platforms
var on_moving_platform      = collision_line(bbox_left, bbox_bottom + 1, bbox_right, bbox_bottom + 1, obj_platform_moving, false, true)
if on_moving_platform
    return on_moving_platform
    
return false

You’ll notice this is called only if the object is actually moving down in the scr_move script.

Slopes & Moving Platforms

I will be talking about slopes and moving platforms in the next article. You can check Zack’s code in the meantime. The basic concept for slopes is there but beware that for moving platforms I’m using a totally different solution.

That’s all folks

Anyway this is the basic of my platformer engine. Every moving object may execute a custom variation of the previous scripts.

The logic behind it, is the same. Round the speeds while keeping track of the sub-pixel values, move 1px in the direction of the speed for as many times as the value of the speed until we collide with something.

That’s all for now…

About the xprev and yprev.

I might use the xprev and yprev as I see fit for my particular objects. Sometimes I need to know their positions before moving them at all (hence their values get refreshed before the repeat-loops). Sometimes I might need to store those values elsewhere along the execution of the movement code. Those are just values I keep around “just in case”.

What are your thoughts on this?

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: