• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Collision with Moving Veritcal Platforms

Ladi_Pix3l

Member
I understand how different this is to horizonal platforms when it comes to platformers. Vertical platforms seem to be taboo when it comes to tutorials.

my collision code for the player's End Step event
Code:
// Vertical
repeat(abs(vsp)) {
    if (!place_meeting(x, y + sign(vsp), oParSolid))
        y += sign(vsp); //If player don't collide under or above, update y position
    else {
        vsp = 0; // If he collides, can't go further...
        break; // ..and we stop checking vertical collisions this frame
    }
}
Collision code for vertical moving platform
Code:
y = scrWave(ystart,peak,1,0);

if (instance_exists(oPlayerParent))
{
 if (place_meeting(x,y+1,oPlayerParent))
 {
  oPlayerParent.y = y-16;
 }
}
this...…. kinda works. Theres just a lot of skipping and hopping when descending. and I'm not sure on why...
 
A

Annoyed Grunt

Guest
I understand how different this is to horizonal platforms when it comes to platformers. Vertical platforms seem to be taboo when it comes to tutorials.

my collision code for the player's End Step event
Code:
// Vertical
repeat(abs(vsp)) {
    if (!place_meeting(x, y + sign(vsp), oParSolid))
        y += sign(vsp); //If player don't collide under or above, update y position
    else {
        vsp = 0; // If he collides, can't go further...
        break; // ..and we stop checking vertical collisions this frame
    }
}
Collision code for vertical moving platform
Code:
y = scrWave(ystart,peak,1,0);

if (instance_exists(oPlayerParent))
{
 if (place_meeting(x,y+1,oPlayerParent))
 {
  oPlayerParent.y = y-16;
 }
}
this...…. kinda works. Theres just a lot of skipping and hopping when descending. and I'm not sure on why...
Well, of course there is a lot of skipping and hopping! You're making the player jump by 16 pixels regardless of what the actual overlap is, which means:

A) If the overlap is actually 16 pixels or a multiple, it is going to work perfectly
B) If the overlap is smaller than 16 pixels, it is going to lead to the player being pushed further up, making him "hop"
C) If the overlap is larger than 16, then he's going to be pushed up. There is still going to be overlap, so if the overlap is smaller than 16 go to (B), otherwise repeat (C) until the player is pushed out.

This picture illustrates the concept:



There are multiple ways to work around this. You're handling movement through loops and 1-pixel movements, which is not the most efficient way but it works, so let's use the same concept to find our solution:

Code:
y = scrWave(ystart,peak,1,0);

if (instance_exists(oPlayerParent))
{
 while (place_meeting(x,y,oPlayerParent))
 {
  oPlayerParent.y -= 1;
 }
}
It's not that different! Basically, we simply check if the player is overlapping the platform... and we push them one pixel up. Then we check again... is he still there? Oh no! Let's push them a little bit more... and a little bit more... and a little bit more... until they are out of the way :')
 

Ladi_Pix3l

Member
@Annoyed Grunt My apologies for the -16 pixels. For some reason it was the only reason it was partially working. Evidently I'm getting the same results however which probably means my movement code should be adjusted some how or go with a new method of handling platforming
 

Ladi_Pix3l

Member
@Sabnock yeah I've messed around with controlling the y position which resulted in me using y-16.
@NightFrost I actually have this pinned to my bookmarks. Its a fantastic demonstration but I guess I was having a hard time translating it to GML
 
R

Rukiri

Guest
First off there is quite a few ways for this to be done so posting code isn't going to help you as you need to stick to your own paradigm of programming.
But I will tell you how to solve the problem.

You first need to decide if your going to use vectors, I would just makes motion planning easier but does add some level of confusion especially if you do not know what a vector is.
But the functions you will be using are:
Code:
place_free
place_meeting
with (expression)
lengthdir_y
while (expression)
Just keep in mind the "while" constructor can and will break your game if you are not careful...
So now that you know what functions and constructors to use where and how to you express them in meaningful code?

You will NOT be doing any checks with the player object with the moving platform, unless the moving platform is dangerous to the player, breaks after the player lands on the platform, there is no need to do any interaction code with the platform object.

There is a few things we need to do before we continue, you need to either build a hitbox system using sprites and then using instance_deactivate when not in use especially considering for a platformer you want a circular and rectangle hitbox.
You can use a combo of both the collision mask for the circular mask then use a sprite for the body hitbox or vise versa.

So, now that's out of the way let's solve this problem!
In your step(update) method you will want to use with constructor and place_meeting function to check if you're meeting with the platform object then inside that block you'll then use while and place_free to check for collision with the object, the next step is actually simple it's inverting the platform movement to your player so if the platform moves up move the player up by 1 but... no that leads to jittery looking movement so use lengthdir_y(len, dir) and if I remember my directions dir should be 270 if I'm wrong just look up the direction degrees for game maker. len should just be your move_velocity speed variable, I would round so it smooths out the movement.

So have some checks and use a move_down and move_up boolean variable with the platform so it's easy to magnetize the player to the platform.

@Ladi_Pix3l
Use a Vector2 for 2D motion planning so have a macro or just 2 X/Y variables and set x to 0 and y to 1 then set your move_vector2D = [0, 0] or [] doesn't matter and then in your step event x += move_vector2D[@ X]; and y += move_vector2D[@ Y]. using straight numbers with x/y is okay starting out, but once you start doing anything that requires motion planning vectors are a absolute must!


Hope this is enough of an explanation to solve the problem at hand :)
 
Last edited:
R

Rukiri

Guest
@Rukiri Many thanks. At this point I need to look at my code and study the problem. This guide you've given is pretty informative
I would just check Shaun's youtube channel as I do believe he covered moving platforms in one of his tutorials :)
 

Ladi_Pix3l

Member
I would just check Shaun's youtube channel as I do believe he covered moving platforms in one of his tutorials :)
Actually I did but he handled horizontal whch honestly I can easily manage that with my current movement code. Vertical however is something I don't believe works the same way
 

Joe Ellis

Member
Have a look at this if you want a fresh perspective compared to what most tutorials have:


Code:
ground = false
ceiling = false
wall = false
wall_dir = 0

left_x = 1000000
right_x = -1000000
ground_y = 1000000
ceiling_y = -1000000

var l, i = 0, j = 0, my_id = id, block;

with obj_solid
{
if place_meeting(x, y, my_id)
{l[++i] = id}
}

repeat i
{
block = l[++j]

if vsp >= 0 && bbox_bottom < block.bbox_bottom && bbox_top < block.bbox_top && (bbox_bottom - block.bbox_top <= max_vsp)
{
ground_y = min(ground_y, block.bbox_top)
continue
}

if vsp <= 0 && bbox_top > block.bbox_top && bbox_bottom > block.bbox_bottom && (block.bbox_bottom - bbox_top <= max_vsp)
{
ceiling_y = max(ceiling_y, block.bbox_bottom)
continue
}

if hsp > 0 && bbox_left < block.bbox_left && bbox_right < block.bbox_right
{
left_x = min(left_x, block.bbox_left)
continue
}

if hsp < 0 && bbox_right > block.bbox_right && bbox_left > block.bbox_left
{
right_x = max(right_x, block.bbox_right)
continue
}

}



if left_x != 1000000
{
wall_dir = -1
wall = true
hsp = 0
x = left_x - (bbox_right - x)
}

if right_x != -1000000
{
wall_dir = 1
wall = true
hsp = 0
x = right_x + (x - bbox_left)
}

if ground_y != 1000000
{
ground = true
vsp = 0
y = ground_y - (bbox_bottom - y)
}

if ceiling_y != -1000000
{
ceiling = true
vsp = 0
y = ceiling_y + (y - bbox_top)
}
This works with the bounding boxes alone, and doesn't use the while or repeat loop for moving into contact which most tutorials have, so it's alot more efficient and just as precise.
Then when you have this system in place, the code for moving platforms becomes alot simpler, and I can show you that, but for it to work you need to be using this system, so..
 

Joe Ellis

Member
No, there's no loops except the with loop to get the initial collisions, which there'd only be 3-4 at the most normally, then the repeat loop checks the few blocks that are within the player's bounding box and simply moves the player to contact with them using the bounding box coordinates, there isn't actually any need for the while (place_meeting(x - sign(hsp), y - sign(ysp)) cus you already have the coordinates needed with the bounding boxes
 

Ladi_Pix3l

Member
Bare with me while I study this because I not 100 % sure on what's happening with your example. I feel like if I try replicate this code in a different standalone object, it would keep the character floating above air due to the lack initiation but...…. again I think I'm looking at this wrong..
 

Ladi_Pix3l

Member
Okay I got the hang of the code now and was able to create a working player. With that said I'm still getting the same results.

This concept is starting to sound like a lost cause. There has to be something I'm not getting.
 
A

Annoyed Grunt

Guest
Your platform moves downwards faster than your character's gravity can catch up with, so your player falls down for a moment leading to it switching to its falling animations. There are a lot of ways you could handle this, but I think the simplest would be:

  • define current_ground variable for your player. Set it to noone in the create event.
  • At the start of your step event, reset current_ground to noone.
  • In your step event, when you do vertical collision handling, if the player is resting on an instance set its id to current_ground.
  • In your platform's step event, do:
Code:
var new_y = scrWave(ystart, peak, 1, 0),
y_change = new_y - y;
y = new_y;

if (instance_exists(oPlayerParent))
{
if !oPlayerParent.current_ground == id {
 {
  oPlayerParent.y += y_change;
 }
Explanation:
current_ground simply holds what instance your player is standing on. Since your reset it at the start of your step and then set it if a collision happens, either it remains noone after the step or it is set to an instance. This means that if your player is standing still on some ground, sure, current_ground will become false at the start of the step event, but by the end of it it will be back to the ground's id. This means that for everybody except the player between the start and the end of your step event, current_ground will either be noone if the player is in the air or fixed on one ground instance.
The moving ground instance just makes a simple check: it first calculates by how much it will move (y_change), and if it checks that the player is standing on it, it then "pushes" the player in that direction.

Tell me if you have any issues, while I have not tested this code specifically this is almost exactly the logic I used to make moving platforms in my last project.
 

NightFrost

Member
@NightFrost I actually have this pinned to my bookmarks. Its a fantastic demonstration but I guess I was having a hard time translating it to GML
The basic idea on how the code handles "riders" is by transferring the moving platform's movement to all riders. This is accomplished by making the move-and-collision-detect part of movement code into a common script that every moving entity will call to move. When a platform has an entity ride it to some direction, it calls that script for the entity again. In other words, entities may get moved multiple times a step, some due to their own movement, some due to other forces making them move. The "push" is separate action. This happens when the platform moves into collision with an entity; the same move script is called for it again, but it is additionally supplied with an action what to do if the entity cannot be enough - by default, a death because the entity is being squished between platforms.
 

Ladi_Pix3l

Member
Nvm..... it's still skippy…...
@NightFrost Yeah that I got.... but with the way GML is, im not sure vertical platorms (which Is bummer) my game doesn't really need it but it's something I really wanted understand
 

Joe Ellis

Member
Here's an updated version of the first script I sent with the code for moving platforms added:

Code:
///collision_step()

//x += hsp
//y += vsp
ground_id = 0
ground = false
ceiling = false
wall = false
wall_dir = 0

left_x = 1000000
right_x = -1000000
ground_y = 1000000
ceiling_y = -1000000

var l, i = 0, j = 0, my_id = id, block;

with obj_solid
{
if place_meeting(x, y, my_id)
{l[++i] = id}
}

repeat i
{
block = l[++j]

if vsp >= 0 && bbox_bottom < block.bbox_bottom && bbox_top < block.bbox_top && (bbox_bottom - block.bbox_top <= max_vsp)
{
ground_y = min(ground_y, block.bbox_top)
if ground_y = block.bbox_top
{ground_id = block}
continue
}

if vsp <= 0 && bbox_top > block.bbox_top && bbox_bottom > block.bbox_bottom && (block.bbox_bottom - bbox_top <= max_vsp)
{
ceiling_y = max(ceiling_y, block.bbox_bottom)
continue
}

if bbox_left < block.bbox_left && bbox_right < block.bbox_right
{
left_x = min(left_x, block.bbox_left)
continue
}

if bbox_right > block.bbox_right && bbox_left > block.bbox_left
{
right_x = max(right_x, block.bbox_right)
continue
}

}

if left_x != 1000000
{
wall_dir = -1
if hsp > 0
{
wall = true
hsp = 0
x = left_x - (bbox_right - x)
}
}

if right_x != -1000000
{
wall_dir = 1
if hsp < 0
{
wall = true
hsp = 0
x = right_x + (x - bbox_left)
}
}

if ground_y != 1000000
{
ground = true
vsp = 0
y = ground_y - (bbox_bottom - y)

//Moving Platform
if ground_id.object_index = obj_moving_platform //Or use: if object_is_ancestor(ground_id.object_index, obj_moving_platform)
{
if ground_id.hsp != 0
{
x += ground_id.hsp

if wall_dir = -1 && sign(ground_id.hsp) = 1
{
hsp = 0
x = left_x - (bbox_right - x)
}
if wall_dir = 1 && sign(ground_id.hsp) = -1
{
hsp = 0
x = right_x + (x - bbox_left)
}
}
if ground_id.vsp > 0
{y += ground_id.vsp}
//if ground_id.vsp < 0 && ceiling_y != -1000000
//{kill(id)}
}
//
}

if ceiling_y != -1000000
{
ceiling = true
vsp = 0
y = ceiling_y + (y - bbox_top)
}

It's important that the platforms are moved after this script, otherwise the player will end up bouncing slightly on them.
This code will work for moving platforms in any direction, cus the important part is that the player is standing on it in order to inherit the movement.
In any other situation it will just get pushed, which the normal collision code handles anyway.

I also added "x += hsp, y += vsp" to the start of this script, cus it's the best for the position to be updated just before collision is checked.
 

Ladi_Pix3l

Member
with 'my_id' is that created when colliding?
'max_vsp' isn't declared anywhere so this might not run. i'll see about an alternative.

(editing and adjusting code to game)

umm I'm more confused than ever.

Let me see if I get this because there's something wrong here I'm far from understating this
So First: This collision code is meant for the Moving platform
Second: The movement code should be after the collision code for it to work properly
Third: The collision code is supposed move the player with it.

But... the momentum or vertical speed of the player never match. For whatever reason the when gravity is being challenged, it becomes an issue. This does work horizontally however.
 

Joe Ellis

Member
The script is meant to be run in the player's step event, after gravity, acceleration and any other movement is applied to the player's hsp and vsp.
The my_id is just used instead of using place_meeting(x, y, other.id), it's just slightly faster, but you can use either.
Yeah you'll have to declare max_vsp somewhere before hand.

The moving platform doesn't run any collision, it should just have the movement code: x += hsp, y += vsp in the END step event.
It's important that the platform moves after the player has finished running the collision script, cus if not, the player will get moved to contact with the platform but will then also get moved by the platforms speed, which would make the player end up being slightly above or below where it's supposed to be, and sometimes like if the platform is moved downwards before the player moves and checks collision, the player wont detect collision with it and won't be able to inherit the downwards movement.

So if the platform is moved AFTER the player moves, everything will end up being in the right place by the end of the step\draw.
Cus the player will move to contact with the platform, then add the speed from the platform, which will make the player temporarily slightly above or below the platform's surface, but then right after the platform gets moved also, so then the player is in the correct "in contact" position but has also been moved by the platform.

I hope that makes sense, I tried to explain as well as I could
 

Ladi_Pix3l

Member
Okay that's what I thought originally but I was getting the same results. Even now I created a new object with some fresh code and got the same skippy-ness. Does game maker not work well with vertically moving platforms?

For the logic of the moving the player around vertically, I get that. The player has to have his collision code initiate first so the moving platform can actually work with the player. then apply it's following concept afterwards so the collision can easily be met.

But.....the logic here isn't reading in my game for some reason. Has this tested in GMS 2 or 1.4?

I'm actually going to try on 1.4 and give my results
 

Joe Ellis

Member
It was tested in 1.4, but I can't see any reason why it would be different in gms2 cus it's pretty much all maths. It works perfectly in my project, but there must be something causing it to not work in yours outside of that script, its hard to guess what it could be without seeing the rest of your project or at least the entire code for the player. If you showed me that I'd probably be able to pick out the problem pretty quick
 

Joe Ellis

Member
Hi, I got it working :D
There were a few problems or short-comings with my script, cus I've made it for an hd game, so there were problems that happen with pixel games that I hadn't come across.

1. If platform's speed is < 1, the jittering occurs, so I added an extra thing to stop that

2. There was an "off by one" bug, I hadn't noticed cus of the res being high

3. If walls are individual squares, the player will latch onto the wall, cus of it colliding with the top of the squares, even though there is another square above it.
There are a few workarounds for this, the one I did for now was just to make all walls tall rectangles.. This is more efficient anyway, but it might not be suitable for your game, eg. if the blocks are dynamic\destroyable
If you need the blocks to be square, another fix would be at the start of the level, check all blocks and if any square blocks have another block on top of them, set a variable like "no_ground" \ "wall_only" to true, then check this in the collision script.
Another alternative would be to have a separate object for wall & floor, but that would probably be awkward.
If you use tiles for your game, and invisible blocks for the collision, I'd recommend the first option of making rectangular blocks.

Yeah, so I fixed those things, and there were just a few problems outside of the script:

1. The previous collision method you were using was still active in the player's step event

2. The collision_step() was being run in the End step event, I moved it into the end of the normal step event.

3. The platforms movement was in the normal step event, I moved this to the End step event.


And that's it, it should work perfectly now :)

https://drive.google.com/open?id=1e64OHLEGRarpxto3HZpSBQ74Q6TOmFCQ
 

Joe Ellis

Member
Oh yeah I forgot to mention that the vsp needs to be clamped sorry.

It's do to with a cutoff point between classing a block as wall or ground.
If the distance between the player's feet and the top of a block is more than a certain amount, the player won't move up onto that block and stop at the
side of it.
Say if you make a staircase of blocks, each step about 8 pixels tall, the player is able to walk up them. So this is useful if there is a slight bump in the ground, the player wont stop dead at it.

So this cutoff point needs to be the maximum the player can move in one frame, (or more) cus if the vsp is more than this, the ground collision is ignored, making the player fall through it. There might be another solution for this but I haven't worked it out yet, if I do I'll let you know.
 
Top