Moving Platforms - Horizontal and Vertical ⋆ 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.

MacOS Crash Update – 2020-04-19

A reader kindly reported this bug to YoYo Games (attaching the previous version of the project). You can read the bug report here: 0031657: macOS: Runner crashes during collision check in the attached project

It’s due to “bailing from a repeat within a with” and it’s marked as fixed for the upcoming version 2.3.0.

MacOS Crash Warning (see update above)

The following code will probably break on MacOS. Along with YoYo Games we are currently investigating the culprit. If I had to make a wild guess, I’d say that the MacOS runner has some weird bug about nested context and recursion that makes it lose track of who’s doing what, eventually crashing without any warning. I’m confident that YoYo will get back to me with some good news but in the meantime I’m publishing this article anyway… because this is how I make moving platforms 🤷‍♂️

I’m not saying this code is bug free. It might very well be a sneaky bug in my code. But to this date, I still haven’t found it. If you spot it, please point it out.

I’ve seen a lot of bad implementations of moving platforms in GameMaker Studio, especially the vertical moving platforms. Some rely on the fact that, due to gravity, the player will naturally follow a downward moving platform (which is odd, and wrong), whilst others don’t even try to explain the vertical moving platforms at all. Like they don’t exist.

I’m not sure what’s going on here. Maybe my code is super naive and I’m not doing it right either, but moving platforms don’t have to be difficult. They’re objects that live in the begin_step event; they do their collision checks, they can push or carry other objects (or squish them or whatever you decide) and… and that’s it, really.

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

The player will naturally collide with these platforms but only in the step event, when the platforms have already completed their movements so there won’t be any odd behavior. To entities, moving platforms are… not really moving at all. I’d say that entities are, to moving platforms, also not moving at all. Remember that they live in two separate events.

Basic platforms vs Advanced platforms

The following article will describe basic horizontal and vertical platforms which can push and carry the player. These are not advanced moving platforms, able to carry and push more than one item. Those kind of platforms need slightly more work in the collision detection area and the colliding instances must be handled differently. Don’t worry though, they will be covered in the next article. If you need moving platforms for the player though, the following article is what you need!

Fixes and cleanups

Some fixes before we begin

Before we begin I need to fix my movement code. Let’s create a platformer_init script and put these lines inside it. I introduce a couple of new variables here for clarity purposes. I like to know that xvel_int and yvel_int will always hold an integer number no matter what.

Also remember to call the platformer_init() script in the create event of the player.

///@func platformer_init()xvel= 0yvel= 0xvel_int= 0 // Integer x speedyvel_int= 0 // Integer y speedxvel_fract = 0yvel_fract = 0
Moving Platforms - Horizontal and Vertical ⋆ Nikles dabbling in Game Dev (1)

Also open the oPlayer object and slightly alter the movement lines in the step event to accommodate these changes.

// Use the xvel_int hereif !move_x(xvel_int, true){ xvel = 0 xvel_fract = 0}// Use the yvel_int hereif !move_y(yvel_int){ yvel = 0 yvel_fract = 0}

There’s one last piece of code I need to alter: round_vel()

///@func round_vel()///@desc Round the xvel/yvel while keeping track of fractionsxvel_fract += xvel;xvel_int = floor(xvel_fract); // use xvel_int herexvel_fract -= xvel_int; // and hereyvel_fract += yvel;yvel_int = floor(yvel_fract); // Use yvel_int hereyvel_fract -= yvel_int; // and here

Other than using the new variables, I decided to go with flooring, instead of rounding. It should work either way but I prefer to know we always round down the velocities.

New collision scripts

coll_x and coll_y to simplify collision checks

I’ve introduced a couple of new scripts to avoid having to write collision checks over and over. Let’s see how they work. Passing a direction, will automatically select the correct side to check. Also the obj parameter is optional.

///@func coll_x(xdir, [obj])var xdir = argument[0]var obj = argument_count == 2 ? argument[1] : oWallvar side_to_check = xdir ? bbox_right + 1 : bbox_left - 1return collision_rectangle(side_to_check, bbox_top, side_to_check, bbox_bottom, obj, false, true)
///@func coll_y(ydir, [obj])var ydir = argument[0]var obj = argument_count == 2 ? argument[1] : oWallvar side_to_check = ydir ? bbox_bottom + 1 : bbox_top - 1return collision_rectangle(bbox_left, side_to_check, bbox_right, side_to_check, obj, false, true)

Ternary operator

The ternary operator is just a shorthand to write an if / else statement. It’s usually used in assignment statements and it works like this:

some_var = (condition) ? value_if_true : value_if_false

Moving Platforms

Let’s see how to implement uncomplicated moving platforms

First of all create a new sprite sMoving (maybe duplicate the wall and give it another color) and assign it to a new object named oMoving. Let’s make this object a child of oWall. And voilà!

With this single action you’ve already taken care of every possible collision that might happen in the player’s step event. Indeed if you were to run the game now with a couple of these oMoving objects in the game, they’d be sitting still, of course, but the player cannot go through them. He can stand on them just like any other oWall. And from the player’s side of things, it’s going to stay this way.

Moving Platforms - Horizontal and Vertical ⋆ Nikles dabbling in Game Dev (3)

Moving on

Let’s add some movement

Now let’s see what happens when we begin to move the moving platforms. Spoiler alert: they do not collide with the player.

Open the create event of the oMoving platforms and initialize the platformer by calling platformer_init(). Then open the begin step event and place the movement code inside it.

/// @desc moveround_vel()// Let the instance decide what to do when it can't moveif !move_x(xvel_int, false){ xvel = -xvel // Reverse the speed in case of collision xvel_fract = 0}if !move_y(yvel_int){ yvel = -yvel // Reverse the speed in case of collision yvel_fract = 0}

This is the very same code we have inside the oPlayer object slightly altered to let the moving platforms invert their speed when colliding with walls. Now, for the moving platforms to actually move we need to define an initial speed. We can do so in the room editor with the creation code.

Creation code

Creation code is code that is run after the create event for each instance. Meaning that, as an instance is created, its create event runs, then its creation code runs and then GameMaker go on creating another instance and so on. This is a good place to override values initialized in the Create event.

If you run the game now, you end up with this. Disappointed? That’s exactly the result we expected though.

Moving Platforms - Horizontal and Vertical ⋆ Nikles dabbling in Game Dev (5)

This is what might get some people confused. It’s true that the player cannot go inside the platforms but the platforms can go inside the player. While the player’s collisions are already set correctly (since the moving platforms are children of oWall), the oMoving platforms need some more work.

We can’t use the same movement and collision code that we use in the oPlayer object. Indeed those scripts don’t take into account collisions with the oPlayer itself (of course) so to the moving platforms, there is no collision at all. We need to define movement and collision detection specific for the moving platforms.

Complexity

If those move_x and move_y scripts were complex enough to take into account each object’s own characteristics, they would work. For instance, if every object defined a list of what if could collide with and what it could carry or push, those script could read the list and then behave accordingly. But for the sake of this article, they’re not that complex. And I don’t want to complicate this too much. We’ll explore such generalized solutions in more advanced engines.

Platforms’ own movement scripts

move_platform_x and move_platform_y to the rescue

So let’s change the platform’s begin step event code into this, introducing move_platform_x and move_platform_y scripts.

In these scripts we take into account collisions with oPlayer from the sides and from above.

Let’s create them and let’s see how they work. First is the horizontal movement. The idea is to return false whenever the platform encounters and unmovable obstacle (such as walls or a stuck player).

/// @desc moveround_vel()if !move_platform_x(xvel_int){ xvel = -xvel xvel_fract = 0}if !move_platform_y(yvel_int){ yvel = -yvel yvel_fract = 0}

The collision from the sides may return false in case the player cannot move at all (e.g. squashed between a moving platform and a solid). Here is where you decide what to do in your game (I just return false and let the platform reverse its speed but you might as well kill the player). It’s also where the MacOS crashes, btw.

The collision with the player standing on the platform, on the other hand, does not return anything. This is because the platform doesn’t care about the player being unable to move. It will simply slide off its feet and continue its movement.

///@func move_platform_x(xvel_int)///@arg xvelvar _xvel = argument[0]var _xdir = sign(_xvel)// Movement/Collision Xrepeat(abs(_xvel)){ // Colliding with solid if coll_x(_xdir) return false // Pushing the player var player_on_sides = coll_x(_xdir, oPlayer) if player_on_sides && false == move_x(_xdir, true, player_on_sides) return false // Squashed between solids // Carrying the player // Notice how we don't care if the player // can't move in this case. The underlying platform will // simply slide off its feet (hence we don't return false) var player_on_top = coll_y(-1, oPlayer) if player_on_top move_x(_xdir, false, player_on_top) // Finally move x += _xdir}return true

Taming the vertical movement

With a trick

The following script is for vertical movement instead. If the platform is going upward, in case of player collision, we simply try to push it upward as well. No big deal. As usual we return false if the player is stuck (or you might kill it). Note: this is where the macOS crashes.

Here comes what might confuse someone. If we’re going downward, we move the player downward as well via its own move_y script. But if we’re carrying it on top of the platform, we need deactivate the platform during player’s collision checking.

If we don’t do this, the player would collide with the very platform that is trying to move it, before being able to move downward. This collision will leave the player behind in mid-air, making it bounce on the platform on its way down. That’s why we deactivate and reactivate the moving platform really quickly, just to remove the instance from the possible collisions.

It’s like saying “Not considering this very platform, try moving the player down 1px”.

///@func move_platform_y(yvel_int)///@arg yvelvar _yvel = argument[0]var _ydir = sign(_yvel)repeat(abs(_yvel)){ // Colliding with solid if coll_y(_ydir) return false // Going upward if !_ydir { // Carry the player upward (lift it) var player_above = coll_y(_ydir, oPlayer) if player_above && false == move_y(_ydir, player_above) return false } // Going downward if _ydir { // Push the player downward if it's colliding from below var player_below = coll_y(_ydir, oPlayer) if player_below && false == move_y(_ydir, player_below) return false // Carry the player downward with the platform if it's standing on top of the platform var player_above = coll_y(-1, oPlayer) instance_deactivate_object(self) // Dirty trick begins if player_above move_y(_ydir, player_above) instance_activate_object(self) // Dirty trick ends } // If everything went good, move y += _ydir}return true

And just like that, believe it or not, the moving platforms are done. There’s nothing else to do to have horizontal and vertical moving platforms capable of carrying and pushing the player around without odd, jerky movements.

Moving Platforms - Horizontal and Vertical ⋆ Nikles dabbling in Game Dev (6)

If you are curious about more advanced platforms, able to carry more than one item, simply stay tuned for the next iteration about advanced moving platforms.

On returning false

As a reader pointed out, some of my scripts return 0 instead of return false. Most of the time it doesn’t make a difference but for clarity, I decided to replace all instances of return 0 with return false in scripts such as move_x, move_y and others. You can do a project-wide search and replace if you want to do so as well (or just download my project).

Moving Platforms - Horizontal and Vertical ⋆ Nikles dabbling in Game Dev (2024)
Top Articles
Latest Posts
Article information

Author: Arielle Torp

Last Updated:

Views: 5547

Rating: 4 / 5 (41 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Arielle Torp

Birthday: 1997-09-20

Address: 87313 Erdman Vista, North Dustinborough, WA 37563

Phone: +97216742823598

Job: Central Technology Officer

Hobby: Taekwondo, Macrame, Foreign language learning, Kite flying, Cooking, Skiing, Computer programming

Introduction: My name is Arielle Torp, I am a comfortable, kind, zealous, lovely, jolly, colorful, adventurous person who loves writing and wants to share my knowledge and understanding with you.