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.
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
, speed
, direction
, 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.
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”.
I found it very interesting how the heart animation happens in your hud. Would you be able to teach me or give me a clue on how to get to this incredible result?