SOLVED weird problem with my collision code, when going into a block

madsholme

Member
I am using GM wolfs tilebased collision. And it works fine if i just jump around. The problems happens when i go into a block the player gets shoot across the room. Any idea on why this happens? here are 2 gifs showing the problem:


GML:
    key_right = keyboard_check_direct(vk_right);
    key_left = keyboard_check_direct(vk_left);
    jump  = keyboard_check(vk_up);
    key_down = keyboard_check(vk_down);
    //jump = keyboard_check_pressed(ord("Z"));
       
   
   
    //Calculate Movement
    move = (key_right - key_left) * SPD_WALK
    hsp = move;
    vsp += SPD_GRAVITY
   
    //Re apply fractions
    hsp += hsp_fraction;
    vsp += vsp_fraction;
   
    //Store and Remove fractions
    //Important: go into collision with whole integers ONLY!
    hsp_fraction = hsp - (floor(abs(hsp)) * sign(hsp));
    hsp -= hsp_fraction;
    vsp_fraction = vsp - (floor(abs(vsp)) * sign(vsp));
    vsp -= vsp_fraction;
   
    x = x + hsp;
    y = y + vsp;
   
   
   
    #region COLLISION CODE
    //tilemap collision
    if (vsp > 0) && (!place_meeting(x,y+vsp,obj_block)){ //down movement
   
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_bottom & ~15) -1) - sprite_bbox_bottom
        vsp = 0;
        onGround = true;
        jumps = jumpsmax;
       
    }
       
    } else { // up movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_top + 16) & ~15) - sprite_bbox_top
        vsp = 0;
       
    }
    }
   
    //horts collision
   
    if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~15) - 1) - sprite_bbox_right
        hsp = 0;
    }
   
    } else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 16) & ~15) - sprite_bbox_left
        hsp = 0;
    }
    }
    #endregion
   
   
   
   
    //Jump
            if (jump) && (jumps > 0)
            {
            //sprite_index = sPlayer_Jump_Base;
            //image_speed = 0.5;
            //scr_screenShake(2,10);
            //audio_play_sound(sound_Jump, 10, false);
            //jumps -= 1          
            vsp = -SPD_JUMP;
            onGround = false;
            jumps -= 1

        }
 
Last edited:

madsholme

Member
Your t2 is improperly defined here.
thanks but it still happens. if i jump on and down there is no isue. if i tough a side either left or right i fall through the floor.
this is the code now


GML:
#region COLLISION CODE
//tilemap collision
if (vsp > 0) && (!place_meeting(x,y+vsp,obj_block)){ //down movement
  
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_bottom & ~15) -1) - sprite_bbox_bottom
        vsp = 0;
        onGround = true;
        jumps = jumpsmax;
      
    }
  
} else { // up movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_top + 16) & ~15) - sprite_bbox_top
        vsp = 0;
      
    }
}

//horts collision

if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~15) - 1) - sprite_bbox_right;
        hsp = 0;
    }
  
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 16) & ~15) - sprite_bbox_left;
        hsp=0;
    }
}
#endregion
i seem to be closer to figure out whats wrong. now if i move my movement code below the collision code i stop falling throught the floor when i touch the side, but i can sometimes climb over stuff, and i think thatis becaus i move inside the object when i do a movement after the collision, instead of first.

the first ones are testing my object collision. and the seccond one is tile collision test. as you can see i can climb over them both.

GML:
/// @description Insert description here
// You can write your code in this editor
//Get inputs
key_right = keyboard_check_direct(vk_right);
key_left = keyboard_check_direct(vk_left);
jump  = keyboard_check(vk_up);
key_down = keyboard_check(vk_down);
//jump = keyboard_check_pressed(ord("Z"));
    


//Calculate Movement
move = (key_right - key_left) * SPD_WALK
hsp = move;
vsp += SPD_GRAVITY

//Re apply fractions
hsp += hsp_fraction;
vsp += vsp_fraction;

//Store and Remove fractions
//Important: go into collision with whole integers ONLY!
hsp_fraction = hsp - (floor(abs(hsp)) * sign(hsp));
hsp -= hsp_fraction;
vsp_fraction = vsp - (floor(abs(vsp)) * sign(vsp));
vsp -= vsp_fraction;



 #region Block collision
//block collision
//vert col
if (place_meeting(x,y+vsp,obj_block))
{
    while (!place_meeting(x,y+sign(vsp),obj_block))
    {
            y = y+sign(vsp);
    }
    vsp = 0;
    onGround = true;
    jumps = jumpsmax;
}
//hortz col

if (hsp != 0 && place_meeting(x+hsp,y,obj_block))
{
   if !place_meeting(x+sign(hsp),y-32,obj_block)
   {y -= 32}


   else
   {
  
   while (!place_meeting(x+sign(hsp),y,obj_block)) x += sign(hsp);
   hsp = 0;
   }
  
}
#endregion



#region COLLISION CODE
//tilemap collision
if (vsp > 0) && (!place_meeting(x,y+vsp,obj_block)){ //down movement
    
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_bottom & ~15) -1) - sprite_bbox_bottom
        vsp = 0;
        onGround = true;
        jumps = jumpsmax;
        
    }
    
} else { // up movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_top + 16) & ~15) - sprite_bbox_top
        vsp = 0;
        
    }
}

//horts collision

if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~15) - 1) - sprite_bbox_right;
        hsp = 0;
    }
    
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 16) & ~15) - sprite_bbox_left;
        hsp=0;
    }
}
#endregion


x += hsp;
y += vsp;

//Jump
        if (jump) && (jumps > 0)
        {
            //sprite_index = sPlayer_Jump_Base;
            //image_speed = 0.5;
            //scr_screenShake(2,10);
            //audio_play_sound(sound_Jump, 10, false);
            //jumps -= 1           
            vsp = -SPD_JUMP;
            onGround = false;
            jumps -= 1

        }
 

TheouAegis

Member
EDIT: I had some typoes

You don't need the tilemap collision code anymore if you are going to use obj_block now. But that shouldn't be the issue here...

An idea I had is to change the x shift on collision. Instead of
x = ((bbox_right & ~15) - 1) - sprite_bbox_right;
for a collision on the right, I would try
x += (bbox_right & ~15)-1 - bbox_right;

And for left collision, i would try
x += (bbox_left & ~15)+16 - bbox_left;
 
Last edited:

madsholme

Member
EDIT: I had some typoes

You don't need the tilemap collision code anymore if you are going to use obj_block now. But that shouldn't be the issue here...
i kindt of want both things, sorry if that was not clear. Both seem to be working just with the isue of me being able to climb over walls. as it seems i am moving a bit into them, before being pushed out.

An idea I had is to change the x shift on collision. Instead of
x = ((bbox_right & ~15) - 1) - sprite_bbox_right;
for a collision on the right, I would try
x += (bbox_right & ~15)-1 - bbox_right;

And for left collision, i would try
x += (bbox_left & ~15)+16 - bbox_left;
that just gives me other isues.

 

madsholme

Member
I think i may have missed that the collision test is using the player object, and that object is 32px and not 16px like my tile map. so if i change it to:

GML:
#region COLLISION CODE
//tilemap collision
if (vsp > 0) && (!place_meeting(x,y+vsp,obj_block)){ //down movement
    
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_bottom & ~31) -1) - sprite_bbox_bottom
        vsp = 0;
        onGround = true;
        jumps = jumpsmax;
        
    }
    
} else { // up movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        y = ((bbox_top + 32) & ~31) - sprite_bbox_top
        vsp = 0;
        
    }
}

//horts collision

if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~31) -1) - sprite_bbox_right;
        hsp = 0;
    }
    
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 32) & ~31) - sprite_bbox_left;
        hsp=0;
    }
}
#endregion
it seems to be working better, but when i change the vertical collision like this, it makes the player jump up and down.

and i can still climb over my object based collisions even though it its made with 32px size.

Code:
 #region Block collision
//block collision
//vert col
if (place_meeting(x,y+vsp,obj_block))
{
    while (!place_meeting(x,y+sign(vsp),obj_block))
    {
            y = y+sign(vsp);
    }
    vsp = 0;
    onGround = true;
    jumps = jumpsmax;
}
//hortz col

if (hsp != 0 && place_meeting(x+hsp,y,obj_block))
{
   if !place_meeting(x+sign(hsp),y-32,obj_block)
   {y -= 32}


   else
   {
  
   while (!place_meeting(x+sign(hsp),y,obj_block)) x += sign(hsp);
   hsp = 0;
   }
  
}
#endregion
 

Ido-f

Member
The key is being very precise about where you test for collisions and where you snap the player to after colliding.

An important distinction is, surprisingly, the difference between "positive movement" (right and down) to "negative movement" (left and up).
The reason being that the bbox values indicate the top-left point of their pixel: In negative movement, that's the very edge of the bounding box and the correct place to be checking for a collision, as well as the correct reference point for snapping.
But in positive movement, it is not the edge of the bounding box! bbox_right represents the left side of its pixel while the actual edge is its right side: bbox_right + 1.

So I suggest you try to add a +1 to any bbox_right and bbox_bottom values. In the snapping part too - You want to snap using the tile you detected a collision with, at bbox_right+1. bbox_right alone will sometimes reference the previous tile instead.

Edit: I'm sorry, I realised now that with the way your collision check is laid out, my suggestion would likely cause more problems. Hopefully it still helped in some way.
 
Last edited:

TheouAegis

Member
Hold up. Number crunching time.

32x32 sprite full bounds
Origin at (16,31)
If right collision precisely at x:32...
x = (32 & ~31)-1-31; x = 0

Shouldn't you be adding sprite_xoffset or sprite_yoffset back in? E.g.,
x = (bbox_right & ~31) - 1 - sprite_bbox_right + sprite_xoffset;
 

Ido-f

Member
Wait, aren't you using the last step's position when you use the bbox value? Those don't update until you modify the x and/or y variables. You want to use bbox+speed I think, instead of just bbox
 

Yal

šŸ§ *penguin noises*
GMC Elder
Yeah, the teleport issue happens because you're already inside a wall at the start of the collision code, and since you have a while loop that keeps going infinitely until the player is out of a collision, it'll just move away at the speed of light in whatever direction you try first.
 

madsholme

Member
Hold up. Number crunching time.

32x32 sprite full bounds
Origin at (16,31)
If right collision precisely at x:32...
x = (32 & ~31)-1-31; x = 0

Shouldn't you be adding sprite_xoffset or sprite_yoffset back in? E.g.,
x = (bbox_right & ~31) - 1 - sprite_bbox_right + sprite_xoffset;
i tried making it like so:

GML:
//horts collision

if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        //x = ((bbox_right & ~31) -1) - sprite_bbox_right;
        x = (bbox_right & ~31) - 1 - sprite_bbox_right + sprite_xoffset;
        hsp = 0;
    }
    
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        //x = ((bbox_left + 32) & ~31) - sprite_bbox_left;
        x = ((bbox_left + 32) & ~31) - sprite_bbox_left + sprite_xoffset;
        hsp=0;
    }
}
#end
but it just gave this result
also tried to use - sprite_xoffset without any luck.

Wait, aren't you using the last step's position when you use the bbox value? Those don't update until you modify the x and/or y variables. You want to use bbox+speed I think, instead of just bbox
if you mean like this:

Code:
if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~31) -1) - sprite_bbox_right + speed;
        
        hsp = 0;
    }
    
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 32) & ~31) - sprite_bbox_left + speed;
        
        hsp=0;
    }
}
I tried using both speed, hspeed and spd_walk that is my movespeed var. same result. Am i miss understanding something ?


also it seems my player sprite is moving up and down a little again. one px when it stand still.
 

TheouAegis

Member
i tried making it like so:

GML:
//horts collision

if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        //x = ((bbox_right & ~31) -1) - sprite_bbox_right;
        x = (bbox_right & ~31) - 1 - sprite_bbox_right + sprite_xoffset;
        hsp = 0;
    }
   
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        //x = ((bbox_left + 32) & ~31) - sprite_bbox_left;
        x = ((bbox_left + 32) & ~31) - sprite_bbox_left + sprite_xoffset;
        hsp=0;
    }
}
#end
but it just gave this result
also tried to use - sprite_xoffset without any luck.



if you mean like this:

Code:
if (hsp > 0){ //right movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_right, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_right & ~31) -1) - sprite_bbox_right + speed;
       
        hsp = 0;
    }
   
} else { // left movement
    var t1 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_top) & tile_index_mask;
    var t2 = tilemap_get_at_pixel(tilemap, bbox_left, bbox_bottom) & tile_index_mask;
    if( t1 != 0 || t2 != 0){
        x = ((bbox_left + 32) & ~31) - sprite_bbox_left + speed;
       
        hsp=0;
    }
}
I tried using both speed, hspeed and spd_walk that is my movespeed var. same result. Am i miss understanding something ?


also it seems my player sprite is moving up and down a little again. one px when it stand still.
Oh, my bad, I found the Create Event code, sprite_bbox_right already takes xoffset into account. While we're on that, double-check your Create event doesn't have any typos, like mixing "x" and "y" up.
 

madsholme

Member
Oh, my bad, I found the Create Event code, sprite_bbox_right already takes xoffset into account. While we're on that, double-check your Create event doesn't have any typos, like mixing "x" and "y" up.
np problem, already triple checked my create code, and it seems fine. But here it is:

GML:
#macro SPD_WALK 3
#macro SPD_GRAVITY 0.75
#macro SPD_JUMP 7



//tilemap col
var l = layer_get_id("collision_map");
tilemap = layer_tilemap_get_id(l);
//sprite info
sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);



hsp = 0;
vsp = 0;
hsp_fraction = 0;
vsp_fraction = 0;


jump  = false;
jumpsmax = 100;
 

TheouAegis

Member
Ok, so last night I got to thinking about that Create Event. My biggest peeve with it is it does not allow you to swap sprites with differing bounding box dimensions. In other words, if Sprite A has its xoffset 4 pixels away from the left bounds and 3 pixels away from the right bounds, and has its yoffset 31 pixels away from the top bounds and 0 pixels away from the bottom bounds, then if Sprite B has its xoffset 16 pixels away from the left bounds and 15 pixels away from the right bounds, and has its yoffset 7 pixels away from the top bounds and 0 pixels away from the bottom bounds, then you would not be able to alternate between Sprite A and Sprite B with this code. It's a pretty simple fix -- just set sprite_bbox_left et al every time you change sprites -- but a lot of beginners would not come up with that on their own.

That's been one of my biggest peeves with a lot of these tutorials. I'm impressed by some of them, but in their efforts to make their code all-encompassing, they get distracted and leave some very important things out and then we're left explaining corrections to them. I don't watch the tutorials, so I'll be fair and say sometimes the "student" misses a lot of important information in the tutorial, especially the video ones. Many of those expect you to have already some very basic grasp of coding in Game Maker, such as where semicolons are used, or the difference between parentheses and curly brackets. Still, I know some of these tutorials leave out useful - or important - information about the concepts they cover. So...

I made up a test project to figure out why my codes haven't been working for people. ...They didn't work for me either. (lol) But I did get it to work properly and I think figure out some flaws in the tutorial (again, I haven't watched the tutorial).

First thing I encountered, since I use GMS1.4, is the tile grid in the room needs to be the same size as the snap value, or the tile size in this case. Of course this isn't an issue in GMS2, since the tilemap is automatically sized to the tiles and as long as you use the tile size, everything should be hunky-dory. However, you can shift tilemaps around and doing so will throw off your calculations. This causes th same issues as not setting your grid in the room editor properly in GMS1.4 (you can also shift tiles and tile layers in GMS1.4 just like in GMS2).

Second issue I encountered, which I don't think is present in the tutorial (yay!), but was present in my code (boo...) is when reading bbox_right and bbox_bottom values, you need to add 1 due to how GM handles pixel coordinates. Not doing so will cause juddering as the instance won't actually "collide" until the sprite is overlapping with the tile.

The third and most nerve-wracking issue -- I had to sleep on it and the solution came to me in the shower -- is you must check for a collision immediately after moving along a vector, which means you must not move along both vectors at the same time. I am pretty sure the tutorial does not address this, or if it does, it does so at the very end when most people have already stopped watching the video tutorial. If you move along both vectors at the same time and then handle collisions, this will lead to some horrible corner clipping and your instances will hop wildly about the terrain.

With all that said, here is the code I drafted up last night and fixed up this morning:
GML:
//If shifting all the tiles in the room instead of the camera, this line needs to be called first
x += global.TileShift;

//Determine if we are trying to move left or right
var a = RIGHT - LEFT;
var b = sign(hspd);

var spd;

//Accelerate if moving, decelerate if not
if a != 0 {
    if a==b || b==0 {
        pace++;
        if pace > 15
            pace = 15;
    }
    else
    if pace > 0
        pace--;
}
else
if pace > 0
    pace--;
spd = pace;

if tilemap_get_at_pixel(Tiles,bbox_left,bbox_bottom+1) | tilemap_get_at_pixel(Tiles,bbox_right,bbox_bottom+1) & tile_index_mask {

    //Check if trying to jump
    if JUMP
        vspd = -7;
}
else {
    //Apply gravity and terminal velocity
    vspd += 3/8; 
    if vspd > 8
        vspd = 8;
     
    //Apply drag in air
    if spd
        spd = ceil(sqrt(spd)*2)
}

//Set actual horizontal vector
if a==b || b==0
    hspd = speeds[spd] * a;
else
    hspd = speeds[spd] * b;
 

//Apply vertical vector and check for collision
y += vspd;
var bbottom = y + sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);

//Check if moved into a collision, then move out
if vspd > 0 {
    if tilemap_get_at_pixel(Tiles,bbox_left,bbottom +1) | tilemap_get_at_pixel(Tiles,bbox_right,bbottom +1) & tile_index_mask {
        y += (bbottom + 1 & snap) - bbottom - 1;
        vspd = 0;
    }
}

 
//Apply horizontal vector and check for collision
x += hspd;
var bleft = x + sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
var bright = x + sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);

if hspd > 0 {
    if tilemap_get_at_pixel(Tiles,bright+1,bbox_bottom) | tilemap_get_at_pixel(Tiles,bright+1,bbox_top) & tile_index_mask {
        x += (bright & snap) - bright - 1 + global.TileOffset;
        hspd = 0;
        pace = 0;
    }
}
else
if hspd < 0  {
    if tilemap_get_at_pixel(Tiles,bleft,bbox_bottom) | tilemap_get_at_pixel(Tiles,bleft,bbox_top) & tile_index_mask {
        x += (bleft & snap) - bleft + 1 + ~snap - (-global.TileOffset & ~snap);
        hspd = 0;
        pace = 0;
    }
}
As I said, this was written in GMS1.4, so tilemap_get_at_pixel() is just a custom script that's meant to function similarly. I made macros for tile_index_mask (~0), Tiles (1000000), and snap (~63) because my tiles were 64x64. I didn't use a gravity variable, but as you can see gravity is just 3/8.

I used my own acceleration code, which is what pace and speeds[] are about. Here is the Create event:
GML:
//Horizontal vector
hspd = 0;

//Vertical vector
vspd = 0;

//Array of moving speeds (hard-coded acceleration)
speeds[15] = 4;
for(var i=0; i<15; i++;) {
    speeds[i] = 23/75*i - 2*i*i/750;
}

//Pace (for use with above array)
pace = 0;

//Speed to scroll the tiles in this demo
global.TileShift = -1;

//Tile deviation from grid
global.TileOffset = 0;
Like I said, shifting the tilemap off the grid even the slightest will throw the snapping way off. You need to account for that in the collision checks, if you decide to use tilemap scrolling. I used a global variable global.TileShift to denote which direction the tilemap was going to scroll (-1 for left, +1 for right, 0 for no scroll). You can use this variable by itself for handling auto-scrolling of instances, but it's not useful for collision checks. You need some way to know how far off the grid the tilemap has deviated. That's what global.TileOffset is for. I utilize these variables in the Begin Step event (as well as handling user inputs):
GML:
RIGHT = keyboard_check(vk_right);
LEFT = keyboard_check(vk_left);
JUMP = keyboard_check(ord("C"));

tile_layer_shift(Tiles,global.TileShift,0);
global.TileOffset += global.TileShift;
global.TileOffset &= ~snap;
Now if you go back and look at my horizontal collision code, you can see it handles global.TileOffset differently between left and right movement.
GML:
if hspd > 0 /* || global.TileShift < 0    ;a check like this would be needed if instances don't autoscroll with the tiles */ {
    if tilemap_get_at_pixel(Tiles,bright+1,bbox_bottom) | tilemap_get_at_pixel(Tiles,bright+1,bbox_top) & tile_index_mask {
        x += (bright & snap) - bright - 1 + global.TileOffset;
        hspd = 0;
        pace = 0;
    }
}
else
if hspd < 0 {
    if tilemap_get_at_pixel(Tiles,bleft,bbox_bottom) | tilemap_get_at_pixel(Tiles,bleft,bbox_top) & tile_index_mask {
        x += (bleft & snap) - bleft + 1 + ~snap - (-global.TileOffset & ~snap);
        hspd = 0;
        pace = 0;
    }
}
When global.TileShift is negative, global.TileOffset actually counts down from 63 to 0. I tried keeping global.TileOffset negative, but I just found that letting it be positive made things easier for me. Anyway, when moving right, global.TileOffset functioned perfectly by itself. Thus, in the code for snapping when colliding on the right, all I needed to do was add global.TileOffset to the amount of pixels I needed to jump. This wasn't the case when moving left. I essentially needed to "mirror" global.TileOffset for collisions on the left. I also needed to negate the result because I'm adding to x, not setting it.

Another aspect of my code you'll notice is I only used bbox_* variables when they don't matter. When checking for collisions horizontally, I probably won't need to worry about if bbox_top gets rounded up or rounded down, but I do need to worry about if bbox_left or bbox_right get rounded one way or the other; in fact, I would actually prefer they don't get rounded at all.
GML:
var bleft = x + sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
var bright = x + sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
Game Maker will automatically round off bbox_* variables, which can throw calculations off if you're using a float coordinate in tandem with an integer bbox_* value. As I mentioned at the beginning of this post, you could simplify the sprite_get_bbox_left(sprite_index)-sprite_get_xoffset(sprite_index) and similar parts by performing those calculations every time you change sprites rather than every single step like I did here. If I was working on this seriously, that's probably what I would have done. My point is that your bounding box values should be the same data type as your coordinates. Thus, if x is a float, bleft and bright will also be floats.


I also didn't perform any rounding int he final code. The way my code here works, the instance should be automatically aligned to a pixel horizontally upon collision, but it's still going to have pixel stretching normally. I prefer to round off my coordinates in the PreDraw event and then reapply the fractions in the PostDraw event. This was just a habit I picked up after trying to work out a glitch in an 8-bit code I was working on.
 
Last edited:

madsholme

Member
Thanks for this. and thanks for the explanations there were some things i missed, even though i have played with gamemaker on and off for a few years!
 

TheouAegis

Member
Revision: My code had a lot of issues with negative coordinates because I used bitwise functions and it also moved the player with the tiles even if the player was in the air. That's fine if I was simulating camera movement without a camera, but my ultimate goal was to try to adapt my code to tiles used as moving platforms (I don't recommend it, but it was just something "fun" for me to try out). That opened up a whooooollle lot of issues in my code. It still has a bug or two that I haven't been able to nail down, but many hours later...

Here's my code revised to work with a separate scrolling tile layer. (I removed my actual movement code.)
GML:
//Find the actual bounding box (not GM's rounded ones)
var btop = y + sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
var bbottom = y + sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
var bleft = x + sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
var bright = x + sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);

//Move with scrolling tiles first, or else all other calculations will be off
if global.TileShift != 0 {
    //This first check pushes the instance out of the way when the scrolling tile runs into it
    //If tiles are smaller than the sprite, additional checks will be needed here
    //Buggy if you jump INTO a scrolling tile TOWARD an oncoming edge but not game-breaking
    if (tilemap_get_at_pixel(ScrollTiles,bleft,btop) & tile_index_mask) ^ (tilemap_get_at_pixel(ScrollTiles,bright,btop) & tile_index_mask)
    && sign(hspd) != sign(global.TileShift) {
        x += global.TileShift;
        bleft += global.TileShift;
        bright += global.TileShift;
        hspd = 0;
        pace = 0;
    }
    else
    //This second check carries the instance standing on top of scrolling tiles
    if tilemap_get_at_pixel(ScrollTiles,bleft,bbottom + 1) | tilemap_get_at_pixel(ScrollTiles,bright,bbottom + 1) & tile_index_mask
    && !(tilemap_get_at_pixel(ScrollTiles,bleft,bbox_top) | tilemap_get_at_pixel(ScrollTiles,bright,bbox_top) & tile_index_mask) {
        x += global.TileShift;
        bleft += global.TileShift;
        bright += global.TileShift;
    }
}
  
/*......*/
      
//I had to offset bleft and bright by 1 to clear a wall-hugging issue
if vspd == grav {
     if tilemap_get_at_pixel(Tiles,bleft,bbottom+1) | tilemap_get_at_pixel(Tiles,bright,bbottom+1) & tile_index_mask
     || tilemap_get_at_pixel(ScrollTiles,bleft+1,bbottom+1) | tilemap_get_at_pixel(ScrollTiles,bright-1,bbottom+1) & tile_index_mask {

        //Check if trying to jump
        if JUMP
            vspd = -7;
    }
}
/*......*/

//Apply vertical vector and check for collision
//You could probably do horizontal first, but I think this order is slightly faster
y += vspd;
bbottom += vspd;

//Check if moved into a collision, then move out
if vspd > 0 {
    //Typical jump-through platform code, check for collisions below the sprite only
    //You can use bbottom-vspd if btop causes issues
    //Make sure short-circuit evaluations are enabled
    if (!(tilemap_get_at_pixel(Tiles,bleft,btop) | tilemap_get_at_pixel(Tiles,bright,btop) & tile_index_mask)
    && tilemap_get_at_pixel(Tiles,bleft,bbottom+1) | tilemap_get_at_pixel(Tiles,bright,bbottom+1) & tile_index_mask)
    ||
    (!(tilemap_get_at_pixel(ScrollTiles,bleft,btop) | tilemap_get_at_pixel(ScrollTiles,bright,btop) & tile_index_mask)
    && tilemap_get_at_pixel(ScrollTiles,bleft+2,bbottom+1) | tilemap_get_at_pixel(ScrollTiles,bright-1,bbottom+1) & tile_index_mask) {
        y += (bbottom + 1 & snap) - bbottom - 1;
        vspd = 0;
        bbottom = y + sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
    }
}

//Apply horizontal vector and check for collision
x += hspd;
bleft += hspd;
bright += hspd;

//The commented bits are left in from when only one tile layer was used
//A lot of the code above this line was added strictly for a dedicated scrolling tile layer

if hspd > 0 /*|| global.TileShift < 0*/ {
    if tilemap_get_at_pixel(Tiles,bright+1,bbox_bottom) | tilemap_get_at_pixel(Tiles,bright+1,bbox_top) & tile_index_mask 
    && !(tilemap_get_at_pixel(Tiles,bright-hspd,bbox_bottom) | tilemap_get_at_pixel(Tiles,bright-hspd,bbox_top) & tile_index_mask) {
        x += (bright /*- global.TileOffset*(bright < 0)*/ + 1 & snap) - bright - 1 /*+ global.TileOffset*/;
        hspd = 0;
        pace = 0;
    }
}

if hspd < 0 /*|| global.TileShift > 0*/ {
    if tilemap_get_at_pixel(Tiles,bleft,bbox_bottom) | tilemap_get_at_pixel(Tiles,bleft,bbox_top) & tile_index_mask
    && !(tilemap_get_at_pixel(Tiles,bleft-hspd,bbox_bottom) | tilemap_get_at_pixel(Tiles,bleft-hspd,bbox_top) & tile_index_mask) {
        x += (bleft & snap) - bleft - snap /** (global.TileOffset || bleft>=0) - (-global.TileOffset & ~snap)*/;
        hspd = 0;
        pace = 0;
    }
}
 
Last edited:
Top