• 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!

GameMaker One-way platforms using tile collision, need help!

  • Thread starter Binary Orange Studios
  • Start date
B

Binary Orange Studios

Guest
Hi guys,

I am attempting to modify some of Shaun Spalding's code for tile collisions to allow for one way platforms.

So far, it seems to be working, but I'm having some trouble trying to make sure that the collision check is only happening if the player is truly above or at the y position of the tile. This is to ensure that if the player should ever find themselves halfway through the tile horizontally, when they are falling they aren't being "snapped" back to the top.

So far, this is what my code looks like:

Code:
#region One Way Collisions

if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) != 0 || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) != 0) {
    
    // Make the player collide with the tile only while falling and truly above or at y position of tile
    if falling and y >= tilemap_get_y(oneway_tilemap) {
        y = y - (y mod 64) + 63 - (bbox_bottom - y);
        vsp = 0;
    }
}

#endregion
It is based off of this code...

Code:
// Vertical collision
if (vsp > 0) bbox_side = bbox_bottom; else bbox_side = bbox_top;
if (tilemap_get_at_pixel(collision_tilemap, bbox_left, bbox_side+vsp) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_right, bbox_side+vsp) != 0) {

    if (vsp > 0) y = y - (y mod 64) + 63 - (bbox_bottom - y);
    else y = y - (y mod 64) - (bbox_top - y);
    vsp = 0;
    
}
I am able to make the player go up through the one-way tile, and when they are traveling down, they are able to collide with the top of it just fine. However, when I am entering through the side of the tile and falling, I will "snap" semi randomly within the tile, until I fall the rest of the way out.

I know that the way to solve this would be to get the bbox_top of the tilemap, and compare it something like this...

Code:
if falling && bbox_bottom >= tilemap_bbox_top { do collision }
...but I'm not sure how to do that, and can't seem to find anything in the manual about that. Am I correct in assuming that only objects have bbox variables? Is there a way to get the bbox of a tilemap at all? Every attempt I have made to access any bbox variables in the tilemap have failed.

Any help and advice is greatly appreciated!
 
I

immortalx

Guest
You can find the top left corner of a tile within the room like this:
tilex = (x div 64) * 64;
tiley = (y div 64) * 64;

That's assuming a 64 X 64 tileset. You can then calculate any offset position you'd like from there.
 

TheouAegis

Member
I think they are easier with tiles and with objects. The tile origin is always at the top of the tile. So you need to check if bbox_bottom+vspeed >= tile's y and if bbox_bottom < tile's y.

Gotta get to work, so i don't have time to look over your code just now.
 
B

Binary Orange Studios

Guest
I think they are easier with tiles and with objects. The tile origin is always at the top of the tile. So you need to check if bbox_bottom+vspeed >= tile's y and if bbox_bottom < tile's y. .
Hmm, I tried this but it completely disabled collision checking at all. I tried reversing the order and still didn't work. Perhaps I'm misunderstanding where to put it?

Also, do you mean that a Tile Set object will always have the top left of the tile as its origin? Will it always override the origin specified in the sprite the Tile Set uses?

You can find the top left corner of a tile within the room like this:
tilex = (x div 64) * 64;
tiley = (y div 64) * 64;

That's assuming a 64 X 64 tileset. You can then calculate any offset position you'd like from there.
Would this return the same result as tilemap_get_y()?

I am quite stumped at how this all may work out, haha!

I'm thinking I must be misunderstanding the methodology for detecting tile collisions, I feel as though this line of my code
Code:
if bbox_bottom >= tilemap_get_y(oneway_tilemap)
should actually be reversed, since up is -y.

Just in case I'm missing something rather obvious, here is my entire code so far in the player's step event. No jumping or gravity is working at the moment, I am controlling everything with the arrow keys.


Code:
/// @desc Move the player

var bbox_side;

right = keyboard_check(vk_right);
left = keyboard_check(vk_left);
up = keyboard_check(vk_up);
down = keyboard_check(vk_down);


hsp = (right - left) * 4;
vsp = (down - up) * 4;

// player is falling if speed is greater than 0
if vsp > 0 {
    falling = true;
    grounded = false;
} else {
    falling = false;
    grounded = true;
}

#region -- Solid Collisions --
// Horizontal collision
if (hsp > 0) bbox_side = bbox_right; else bbox_side = bbox_left;
if (tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_top) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_bottom) != 0) {

    if (hsp > 0) x = x - (x mod 64) + 63 - (bbox_right - x);
    else x = x - (x mod 64) - (bbox_left - x);
    hsp = 0;
    
}

// Vertical collision
if (vsp > 0) bbox_side = bbox_bottom; else bbox_side = bbox_top;
if (tilemap_get_at_pixel(collision_tilemap, bbox_left, bbox_side+vsp) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_right, bbox_side+vsp) != 0) {

    if (vsp > 0) y = y - (y mod 64) + 63 - (bbox_bottom - y);
    else y = y - (y mod 64) - (bbox_top - y);
    vsp = 0;
    
}
#endregion

#region -- One Way Collisions --

if (falling) {

    if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) != 0 || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) != 0) {
        if bbox_bottom >= tilemap_get_y(oneway_tilemap) {
            y = y - (y mod 64) + 63 - (bbox_bottom - y);
            vsp = 0;
        }
    }
}

#endregion

// apply speeds
x += hsp;
y += vsp;
 
I

immortalx

Guest
Would this return the same result as tilemap_get_y()?
To be honest I've never used the function you mention, but I suppose it returns the tilemap's position within the room?
What I posted is a way to get the tile over which the player is currently at, and calculate its top left corner.
so the specific tile's "bbox_top" is actually the tiley value.
 
B

Binary Orange Studios

Guest
To be honest I've never used the function you mention, but I suppose it returns the tilemap's position within the room?
What I posted is a way to get the tile over which the player is currently at, and calculate its top left corner.
so the specific tile's "bbox_top" is actually the tiley value.
I'm sorry, I am having a huge brain fart and can't figure out exactly how to use the example you gave. Been at this too long today I suppose! Do you mind showing me how you intend me to use it?
 
I

immortalx

Guest
OK, let's say at a specific point in time, your player is at position 300,200 and you have a 64 X 64 tilemap.

tilex = (obj_player.x div 64) * 64; // (300 div 64) * 64 = 256
tiley = (obj_player.y div 64) * 64; // (200 div 64) * 64 = 192

These 2 values (256,192) are the top-left coordinates of the tile over which the player is at.
They also happen to be that tile's bbox_left, bbox_top. To calculate bbox_right, bbox_bottom add 64 to each of those values.

I don't know if that's what you want. Maybe I'm misunderstanding because of the language barrier (not a native speaker here).
 
B

Binary Orange Studios

Guest
OK, let's say at a specific point in time, your player is at position 300,200 and you have a 64 X 64 tilemap.

tilex = (obj_player.x div 64) * 64; // (300 div 64) * 64 = 256
tiley = (obj_player.y div 64) * 64; // (200 div 64) * 64 = 192

These 2 values (256,192) are the top-left coordinates of the tile over which the player is at.
They also happen to be that tile's bbox_left, bbox_top. To calculate bbox_right, bbox_bottom add 64 to each of those values.

I don't know if that's what you want. Maybe I'm misunderstanding because of the language barrier (not a native speaker here).
Thank you, this helped a lot! I didn't realize div was a command in GML, I imagine it does the same thing as "/"?

It does appear that tilemap_get_y() was not doing what I expected, and that was a huge factor in my code not working properly. Your code, however, works a treat!
So, thanks to that, I seem to have solved my problem!


Code:
#region -- One Way Collisions --

if (falling) {
    var tileY = (y div 64) * 64;
    
    if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) ) {
        if y+sprite_height/2 <= tileY+64 and y-sprite_height/2 >= tileY {
            y = y - (y mod 64) + 63 - (bbox_bottom - y);
            vsp = 0;
        }
    }
}

#endregion
Another realization that I had was that there was no need for me to check the bbox of the player against the y of the tile, since I already had that information in the tilemap_get_at_pixel() check. That led me to think about using the y variable directly, and then using the sprite_height variable to get the "proper" boundaries of the player.

Basically, my logic was flawed in thinking about using bbox in that way, when I should have been using the y of the player from the get go!

One thing is for sure, I am refactoring this code into a script and backing it up pronto, as this will enable me to really start developing my game very quickly.

Thanks to all who helped me think this through!
 
D

Danei

Guest
div is integer division, that is it throws away the remainder and returns the integer portion of your division. / is just regular division so it will return decimals when appropriate.
 
B

Binary Orange Studios

Guest
Well, as it turns out, I did not solve this issue after all.

It seems that when I added code for gravity, falling, etc., it doesn't work. Previously, I was just controller my horizontal speed and vertical speed directly with the keys, and didn't factor in gravity.

I am having issues with the player landing on a one-way tile. If they fall onto it or jump onto it, they will have a "bouncing" effect, and I am not sure what is causing this at all. It's the first issue I demonstrate in my video below.

Next, my original issue of the player falling through the side of a one-way tile persists, except it does try to snap the player to the top of tile, but it gets stuck anyway. This is the second issue in the video.

Finally, it seems that if I have a one-way tile with a space between one above it, my player will end up on top of the one ABOVE the one I'm actually trying to land on, shown in the video below at the end:

And, here is my code, this is the player's step event:

Code:
/// @desc Move the player

var bbox_side;


right = keyboard_check(vk_right);
left = keyboard_check(vk_left);
up = keyboard_check_pressed(ord("X"));
down = keyboard_check(vk_down);

hsp = (right - left) * 4;

// apply gravity
if !grounded {
    vsp += grav;
} else {
    vsp = 0;
}

// jump
if up && grounded {
    vsp -= jump;
}
#region -- Solid Collisions --
// Horizontal collision
if (hsp > 0) bbox_side = bbox_right; else bbox_side = bbox_left;
if (tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_top) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_bottom) != 0) {

    if (hsp > 0) x = x - (x mod 64) + 63 - (bbox_right - x);
    else x = x - (x mod 64) - (bbox_left - x);
    hsp = 0;
   
}

// Vertical collision
if (vsp > 0) bbox_side = bbox_bottom; else bbox_side = bbox_top;
if (tilemap_get_at_pixel(collision_tilemap, bbox_left, bbox_side+vsp) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_right, bbox_side+vsp) != 0) {

    if (vsp > 0) {
        y = y - (y mod 64) + 63 - (bbox_bottom - y);
    } else {
        y = y - (y mod 64) - (bbox_top - y);
    }  
   
    vsp = 0;
}
#endregion

#region -- One Way Collisions --

// check if the player is falling, or walking onto the platform

if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) != 0 || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) != 0) {

    var tileY = (y div 64) * 64;
    if bbox_bottom >= tileY-64 and vsp >= 0 {  
        y = y + (y mod 64) - 63 + (bbox_bottom - y);
        vsp = 0;
    }
}
#endregion

#region -- Detect If Player Is Grounded --
if (tilemap_get_at_pixel(ground_tilemap, bbox_left, bbox_bottom+1) || tilemap_get_at_pixel(ground_tilemap, bbox_right, bbox_bottom+1)) and vsp == 0 {
   
    var tileY = (y div 64) * 64;
    if bbox_bottom+1 >= tileY-64 {  
        grounded = true;
    }
} else {
    grounded = false;
}
#endregion

// apply speeds
x += hsp;
y += vsp;
Specifically, here is the part for one-way collisions:

Code:
#region -- One Way Collisions --

// check if the player is falling, or walking onto the platform

if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) != 0 || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) != 0) {

    var tileY = (y div 64) * 64;
    if bbox_bottom >= tileY-64 and vsp >= 0 {  
        y = y + (y mod 64) - 63 + (bbox_bottom - y);
        vsp = 0;
    }
}
#endregion
 
D

Danei

Guest
It looks like since you're not grounding the the player when they land on the one-way platform, gravity is repeatedly applying. And then it's not getting caught by the player-is-falling check for some reason... hmm, is there a chance that the player's y position has a decimal, and then when you're setting the new y you're adding that decimal back in to the final y position?
 
B

Binary Orange Studios

Guest
It looks like since you're not grounding the the player when they land on the one-way platform, gravity is repeatedly applying.
Well, I struggled with this for quite some time before I came up with a hacky solution, which is using yet another tile layer to determine the player's grounded state:


Code:
#region -- Detect If Player Is Grounded --
if (tilemap_get_at_pixel(ground_tilemap, bbox_left, bbox_bottom+1) || tilemap_get_at_pixel(ground_tilemap, bbox_right, bbox_bottom+1)) and vsp == 0 {
  
    var tileY = (y div 64) * 64;
    if bbox_bottom+1 >= tileY-64 { 
        grounded = true;
    }
} else {
    grounded = false;
}
#endregion
Then I placed a "Ground" tile under all of the other ones, and that's how my player is grounded. It seems to be working fine, as I jump every time I hit the key, whereas the old method wasn't working every time since grounded was being set to true and false each frame.


hmm, is there a chance that the player's y position has a decimal, and then when you're setting the new y you're adding that decimal back in to the final y position?
I'm not sure, there could be, I guess the way to find out is to add it to my little debug string attached to the player.
 

Slyddar

Member
I do this exact thing in my platformer course, but coded differently, as there are multiple ways to achieve it. One thing that is needed though is integers for these type of collision checks. Try capturing the fractions before the collision checks and then reapplying them the next step.

Add 2 new variables, hsp_decimal and vsp_decimal, and then add this before your collisions:
Code:
hsp_decimal = frac(hsp);
hsp -= hsp_decimal;
vsp_decimal = frac(vsp);
vsp -= vsp_decimal;
Not saying that will solve it completely, as I haven't checked the rest of your code, but for tile based collisions, I've found doing this is required.
 
B

Binary Orange Studios

Guest
I do this exact thing in my platformer course, but coded differently, as there are multiple ways to achieve it. One thing that is needed though is integers for these type of collision checks. Try capturing the fractions before the collision checks and then reapplying them the next step.

Add 2 new variables, hsp_decimal and vsp_decimal, and then add this before your collisions:
Code:
hsp_decimal = frac(hsp);
hsp -= hsp_decimal;
vsp_decimal = frac(vsp);
vsp -= vsp_decimal;
Not saying that will solve it completely, as I haven't checked the rest of your code, but for tile based collisions, I've found doing this is required.
This has helped quite a bit, in fact! Although, my player will still "bounce" on the one-way platforms occasionally, and if the fall onto them from a fair height, they will "vibrate". The video doesn't show the vibrating, unfortunately (so something must be being set every other frame?), but you can see I am having slightly better luck!

I added the code just before I check my collisions:
Code:
// capture decimals
var hsp_decimal = frac(hsp);
var vsp_decimal = frac(vsp);

hsp -= hsp_decimal;
vsp -= vsp_decimal;

#region -- Solid Collisions --
// Horizontal collision
if (hsp > 0) bbox_side = bbox_right; else bbox_side = bbox_left;
if (tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_top) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_side+hsp, bbox_bottom) != 0) {

    if (hsp > 0) x = x - (x mod 64) + 63 - (bbox_right - x);
    else x = x - (x mod 64) - (bbox_left - x);
    hsp = 0;
   
}

// Vertical collision
if (vsp > 0) bbox_side = bbox_bottom; else bbox_side = bbox_top;
if (tilemap_get_at_pixel(collision_tilemap, bbox_left, bbox_side+vsp) != 0 || tilemap_get_at_pixel(collision_tilemap, bbox_right, bbox_side+vsp) != 0) {

    if (vsp > 0) {
        y = y - (y mod 64) + 63 - (bbox_bottom - y);
    } else {
        y = y - (y mod 64) - (bbox_top - y);
    }  
   
    vsp = 0;
}
#endregion

#region -- One Way Collisions --

// check if the player is falling, or walking onto the platform

if (tilemap_get_at_pixel(oneway_tilemap, bbox_left, bbox_bottom+vsp) != 0 || tilemap_get_at_pixel(oneway_tilemap, bbox_right, bbox_bottom+vsp) != 0) {

    var tileY = (y div 64) * 64;
    if bbox_bottom >= tileY-64 and vsp >= 0 {  
        y = y + (y mod 64) - 63 + (bbox_bottom - y);
        vsp = 0;
    }
}
#endregion

#region -- Detect If Player Is Grounded --
if (tilemap_get_at_pixel(ground_tilemap, bbox_left, bbox_bottom+1) || tilemap_get_at_pixel(ground_tilemap, bbox_right, bbox_bottom+1)) and vsp == 0 {
   
    var tileY = (y div 64) * 64;
    if bbox_bottom+1 >= tileY-64 {  
        grounded = true;
    }
} else {
    grounded = false;
}
#endregion



Thanks for this tip, it has definitely helped!

Edited to add, I just bought your course, so hopefully I can have this issue solved and will learn even more! Thanks for posting so I could find out about it! :)
 
Last edited by a moderator:

Slyddar

Member
Edited to add, I just bought your course, so hopefully I can have this issue solved and will learn even more! Thanks for posting so I could find out about it! :)
Thanks for the support! There is a huge amount in the course, so I look forward to seeing how you go with it, and how you apply it to your own games. Good luck!
 
Top