How to code a platformer engine ⋆ Nikles dabbling in Game Dev (2024)

Some adjustments required for GameMaker Studio 2.3

The following article is for pre 2.3 versions of GameMaker Studio 2. It doesn’t take advantage of functions, chained accessors, structs and other new features of GML/GMS2.3+ IDE. Unfortunately I’m unable to update the series for the foreseeable future, but downloading and importing the final project inside the new IDE should trigger the conversion.

It will run correctly. From there you could refactor and clean the code a bit to suit your taste, I guess.

In this series, we’ll see how to code a platformer engine in GameMaker Studio 2. It’s mainly for low resolution games but it can be tweaked to either simulate fluid, hi-res movement or to accomodate optimized collision code for hi-res games.

This engine It will include

  • pixel perfect movement
  • slopes
  • moving platforms
  • jump through platforms
  • other neat features such as smooth sub-pixel movement

Starting from basic movement and collision code with slopes, we’ll build up the player object with rudimentary state machines. In the next articles we’ll build upon this and add enemies, hazards and other engine features.

Series articles:

  • How to code a platformer engine
  • Jump through / one way platforms
  • Platformer engine cleanup and fixes
  • Moving Platforms – Horizontal and Vertical
  • Better moving platforms
  • Even better (high speed) moving platforms

Project Setup

We start by creating some placeholder sprites. These are square sprites (for blocks) and a triangle (for slopes). This is because we want a functioning platformer engine in the fastest time possible. We don’t need to work with the final graphics right now.

How to code a platformer engine ⋆ Nikles dabbling in Game Dev (1)

Let’s create a new project in GameMaker Studio 2 and populate it with some sprites. My own sprites are organized in folders so I don’t overcrowd the navigation panel.

Keep in mind some properties when you design these placeholder sprites.

  • Size: 16×16
  • Origin: top left
  • Collision mask: automatic / rectangle

Slope sprites and their collision mask

Slopes are a different beast. They need a precise collision mask to work correctly with our engine. When you transform an object’s image_xscale or image_yscale, the collision mask stretches and adapts itself to it so there’s no need to create different sprites or objects for those slopes.

How to code a platformer engine ⋆ Nikles dabbling in Game Dev (3)
How to code a platformer engine ⋆ Nikles dabbling in Game Dev (4)

We’ll use just one image for slopes but this doesn’t mean we won’t be able to use different slope angles. This is because we’ll rely on image_xscale for various slope inclinations.

Player placeholder sprite

The placeholder for the player is a rectangle with the following dimensions.

  • Width: 14 pixels
  • Height: 30 pixels

Pay attention to the sprite’s origin; since we’ll flip the player using the image_xscale, just like for slopes, we risk colliding the player’s collision mask with near walls when flipping left/right sides in-game.

That’s why we position the origin in the very middle of the sprite.

You may have noticed that the origin is pushed below the player by 1 pixel. This is for easy placement in the room editor.

This little change to the sprite’s origin will influence the x/y position of the instance in the game so you need to take this into account when coding. I rely on proper bounding boxes for collision checks and changing the origin doesn’t change the bounding box of the sprite but it may lead to odd results if you interchange place_meeting and collision_* functions without considering that the origin position is pushed down below by 1px.

Object Hierarchy

The most important part of this whole engine, is to get the object hierarchy right. You can do so much stuff in GameMaker Studio 2 when you plan the correct hierarchy; it can really simplify your code.

To avoid using the oSolid object in the game, I left it without a sprite. We shall never use it in the room editor.

As you can see from the image below, we differentiate slopes from walls. I say walls but I mean to use walls for floors and ceilings as well. I know this is not proper naming, but I couldn’t come up with a sound nomenclature.

How to code a platformer engine ⋆ Nikles dabbling in Game Dev (7)

Please note that there’s a oSlopeWall object that inherits from slopes. It’s a special block but a very important one for our system. We’ll see its uses later on.

Why are slopes separated?

I used to code slopes in a very different way. They were simple solids and the character would collide with them with its bounding box. Honestly, this looks terrible. The new system allows for some more sensible positioning of the objects moving up or down the slopes. There might be some trade-offs but I haven’t encountered any issue while coding the new system. It works and it looks way better than the previous one.

How to code a platformer engine ⋆ Nikles dabbling in Game Dev (8)
How to code a platformer engine ⋆ Nikles dabbling in Game Dev (9)

Coding the Engine

Is the object on the ground?

This question needs answering in a lot of different contexts. You need to know how to behave based on the answer to this question so it’s essential to get this script right. Because of that, we need a way to tell if an object is on the ground be it on an oWall or a oSlope. For this reason we code a on_ground script like this one.

///@func on_ground()///@desc return instance_id of the colliding ground object or noone if not collidingreturn on_wall() || on_slope()

It’s a simple script but you can already tell it’s doing a lot of things for us. It relies on two other scripts to return the colliding instance below (or noone if none found). In the future, it will signal us even when the player is standing on a jump-though platform.

///@func on_wall()///@desc return instance_id of the colliding instance or noone if none foundreturn collision_rectangle(bbox_left, bbox_bottom + 1, bbox_right, bbox_bottom + 1, oWall, false, true)
///@func on_slope()///@desc return the colliding slope instance or noone if nonereturn collision_point(x, bbox_bottom + 1, oSlope, true, true)

It’s important to note that we’re targeting oWall with bounding box collision checks (a collision_line would have provided the same result but I read somewhere it’s slower than a collision_rectangle) while we’re targeting oSlope with a collision_point, centered in the origin (still using the bounding box for vertical point positioning though, just in case you use different bounding box sizes). Because of this distinction, we’re able to let objects go a little bit “inside” slopes.

Code the Player

Create Event

In the create event we setup some variables for movement (and a very basic state machine which we’ll expand in later posts).

/// @description Basic player objectxaxis = 0yaxis = 0xvel = 0yvel = 0xvel_fract = 0yvel_fract = 0state_current = playerStateGroundstate_is_new = truestate_timer = 0

Step Event

In the step event of the player we do 4 things

  • Grab the input from the keys
  • Apply some arbitrary speed to the xvel and flip the sprite accordingly (or stop the player from moving if no input is detected)
  • Run a simple state machine
  • Finally we run the move_and_collide script which will take those manipulated xvel and yvel speeds and move the player accordingly while managing collisions.
/// @description Basic Player actionskright= keyboard_check(vk_right)kleft= keyboard_check(vk_left)kdown= keyboard_check(vk_down)kup= keyboard_check(vk_up)xaxis= kright - kleftyaxis= kdown - kup// Calculate speedsif abs(xaxis){xvel = 1.23 * xaxisimage_xscale = xaxis}elsexvel = 0// Simple State Machinevar _state_current= state_currentscript_execute(state_current)state_is_new= _state_current != state_currentstate_timer= state_is_new ? 0 : state_timer + 1// Movement after all state calculationsmove_and_collide()

Please note that in a decently coded project, you’d manage the horizontal speed in the states themselves and not in the step event like I did in this example. This is because you’d probably want different physics for ground and air movements.

Player States

Unless you code the player states for ground and air, your character won’t be able to do much, so here they are. We will expand these states and add many other interesting things inside these scripts.

playerStateGround

// Player just entered this state. These actions will be executed only once.if state_is_new{state_is_new = !state_is_new}// We're not on ground so let's change state to airif !on_ground(){state_current = playerStateAirexit}// If we intentionally jump with X key, let's change stateif keyboard_check_pressed(ord("X")){state_current = playerStateAiryvel = -3 // this is the jump speedexit}

playerStateAir

// Player just entered this state. These actions will be executed only once.if state_is_new{state_is_new = !state_is_new}// We're on ground. Let's change the state to ground (unless we're going upwards)if on_ground() && yvel >= 0{state_current = playerStateGroundexit}// Keep adding speed to simulate gravity accelerationyvel += 0.17// If we fall faster than this, let's slow downif yvel > 3yvel = 3

As you can see from the code above, I used so-called magic numbers for gravity acceleration, maximum vertical speed (terminal velocity?) and jump speed. You’d be better off defining some variables in the create event of the player so you could easily edit those values (and know the meaning of those numbers by reading the variable’s names).

Move and Collide script

Finally, we’re going to code movement and collisions! If the player has an horizontal speed of 7.43 pixels, we round it to 7, keep the remainder for the next frame, and for 7 times we check for collisions before moving it by one pixel after each check. This ensures we get pixel perfect collisions.

Granted, this might get expensive for very high resolution games where the player needs to travel hundreds of pixels per step (with hundreds of collision checks per step) but for low resolution games, it works pretty damn well. You could come up with different movement code if you wish. Let me know in the comments what you came up with then.

Notice the slope code inside the horizontal movement. This is what lets us stick the player to slopes when going up or down, without changing player states or messing with vertical speeds.

It basically say “if the object is inside a slope, get it outside by pushing it upwards” (going up a slope) and also “if there’s no ground below but there’s a slope just 1px below, stick to it” (going down a slope).

// Movement/Collision Xxvel_fract += xvel;xvel = round(xvel_fract);xvel_fract -= xvel;xdir = sign(xvel)repeat(abs(xvel)){if !place_meeting(x + xdir, y, oWall){x += xdir// Inside a slope (must go up)while collision_point(x, y - 1, oSlope, true, true){if can_go_up()y--else{// Let's go back (outside slope) and stopx -= xdirxvel = 0xvel_fract = 0break}}// On a slope going downwhile !on_ground() && collision_point(x, y + 1, oSlope, true, true){y++}}else{xvel = 0xvel_fract = 0break}}// Movement/Collision Yyvel_fract += yvel;yvel = round(yvel_fract);yvel_fract -= yvel;ydir = sign(yvel)repeat(abs(yvel)){// Going downif ydir{if !on_ground(){y += ydir}else{yvel= 0yvel_fract= 0break}}// Going upelse{if can_go_up() && !collision_rectangle(bbox_left, bbox_top, bbox_right, bbox_top, oSolid, true, true){y += ydir}else{yvel= 0yvel_fract= 0break}}}

And this is the can_go_up script

///@func can_go_up()///@desc return true if player's not colliding with walls/ceilingsreturn !collision_rectangle(bbox_left, bbox_top - 1, bbox_right, bbox_top - 1, oWall, false, true)

There’s plenty of space for optimizing this script and we’ll see how in the next articles.

On Slopes and the oSlopeWall object

Because the player must be able to go inside slopes, we treat them separately from the usual solids/walls. Due to this difference in collision checking, we need to ensure we place the oSlopeWall at the start, in the middle and at te end of our ramps.

In the next article

In the next article we’ll talk about jump-through platforms and we’ll refactor the code to solve some minor issues. Keep in mind that some limitations of this system can be either avoided altogether with sensible level design or manually fixed via code, case per case. This engine, as of now, isn’t really advanced, yet we briefly touched topics such as state machines and slopes.

Please leave a comment

If you notice any oddity, please let me know in the comments. I’m curious about what would you want me to fix. I’m already planning on some fixes and improvements but maybe you noticed something that I missed.

How to code a platformer engine ⋆ Nikles dabbling in Game Dev (2024)
Top Articles
Latest Posts
Article information

Author: Prof. An Powlowski

Last Updated:

Views: 6553

Rating: 4.3 / 5 (64 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Prof. An Powlowski

Birthday: 1992-09-29

Address: Apt. 994 8891 Orval Hill, Brittnyburgh, AZ 41023-0398

Phone: +26417467956738

Job: District Marketing Strategist

Hobby: Embroidery, Bodybuilding, Motor sports, Amateur radio, Wood carving, Whittling, Air sports

Introduction: My name is Prof. An Powlowski, I am a charming, helpful, attractive, good, graceful, thoughtful, vast person who loves writing and wants to share my knowledge and understanding with you.