• 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 [SOLVED] Help with collision checks... off by 1 pixel.

M

McFlyGold

Guest
Hello everyone,

To start this off, I'm using GameMaker Studio 2 and Runtime v2.1.3.189.

So I seem to be having a small collision issue. I'm using a tutorial by Lewis Clark

In part 2 it gets into the collision code. Now he seemed to fix his collision issue in the video, but for me there seems to be a 1 pixel difference in the collisions. Right now I'm just using a simple 16x32 white sprite as the player object since I have no art for the game and just want to make sure things run smoothly first. I'm liking everything so far except this collision problem.

http://imgur.com/ECpW4Qo

Here's a gif that shows that although the blue square meets the walls. you can still see the player poking through. That means that he's overlapping the walls by 1 pixel that I can see. The sprite is using an automatic collision mask set to rectangle. I've tried changing it to custom, but the results are the same. I've also tried extending the mask out by 1 pixel in all directions, but then the sprite doesn't touch the floor or the walls. it stays 1 pixel away!


Here's the collision code I'm using from the video:

Code:
/// @description scr_player_collision()

/// @function scr_player_collision

// Moving Horizontally /////////////////////////////

if(place_meeting(x+hsp, y, obj_wall))

{

while(!place_meeting(x+sign(hsp), y, obj_wall)){ x += sign(hsp) };

hsp = 0;

}

// Apply speed after checks ////////////////////////

x += hsp;

// Moving Vertically ///////////////////////////////

if(place_meeting(x, y+vsp, obj_wall))

{

while(!place_meeting(x, y+(sign(vsp)/10), obj_wall)){ y += (sign(vsp)/10) };

vsp = 0;

if(place_meeting(x, y+1, obj_wall))// Squish when landing

{

grounded = true;

//x_scale = 1.25;

//y_scale = 0.75;

}

}

// Apply speed after checks ////////////////////////////

y += vsp;
I'm not using his squash and squish part of the code. I didn't want my sprite to do that since my character isn't going to do that.

Any ideas as to why my collision seems to be off. Oh yeah, and I've had to place the origin of my sprite to 1 pixel below the sprite or else the player can't move unless you jump first.

Any help is greatly appreciated!
 
Last edited by a moderator:
M

McFlyGold

Guest
So I'm guessing that because the movement code involves variables that have fractional numbers that this may be the reason that I am not getting Pixel Perfect Collision. Does anyone have any suggestions towards a movement code that will incorporate some sort of inertia in movement? Things like friction, or inertia in air, etc. The code I'm using now had that, but the collision issue is really bothering me.
 

Bentley

Member
So I'm guessing that because the movement code involves variables that have fractional numbers that this may be the reason that I am not getting Pixel Perfect Collision. Does anyone have any suggestions towards a movement code that will incorporate some sort of inertia in movement? Things like friction, or inertia in air, etc. The code I'm using now had that, but the collision issue is really bothering me.
I think you're right. The reason you're not pixel perfect is b/c you're moving at subpixels. Imagine you're 0.9 pixels away from a wall. The condition: if (!place_meeting(x + sign(hspd), y, obj_wall)) is false, as there is a place meeting 1 pixel away. So you stop 0.9 pixels before the wall. At least I think that's how it would play out, I wrote this kind of fast.

You could round your hspd and vspd. That's what I do. But much more intelligent people than me could answer your question better.
 
M

McFlyGold

Guest
I think you're right. The reason you're not pixel perfect is b/c you're moving at subpixels. Imagine you're 0.9 pixels away from a wall. The condition: if (!place_meeting(x + sign(hspd), y, obj_wall)) is false, as there is a place meeting 1 pixel away. So you stop 0.9 pixels before the wall. At least I think that's how it would play out, I wrote this kind of fast.

You could round your hspd and vspd. That's what I do. But much more intelligent people than me could answer your question better.
I put a draw event over the player to check the hsp and even when it was a whole number I still overlap a wall by one pixel. I tried rounding the hsp and vsp but I still kept running into the issue. Either I am not rounding in the right place or I'm really goofing up something bad. Thanks for the reply though, I appreciate it!
 

mimusic

Member
Code:
while(!place_meeting(x+sign(hsp), y, obj_wall)){ x += sign(hsp) };
Note here that sign() will return either 1 or -1, so the current state of the code only works on a per-pixel / whole number basis. You seem to have remedied the issue with your vertical collision check:

Code:
while(!place_meeting(x, y+(sign(vsp)/10), obj_wall)){ y += (sign(vsp)/10) };
Note the difference: for your horizontal, you're checking at position x+sign(hsp) , but for your vertical, you're checking at position y+(sign(vsp)/10). Because of the "/10" in there, your vertical check is accurate to .1 of a pixel. To make it accurate to 0.001, you would divide the sign by 1000 -- or just multiply by 0.001. Make this change to your horizontal and see if it helps. Don't make the precision too high though, because that increases the amount of times the while loop needs to run before stopping.

From a technical perspective, it won't be absolutely perfect, simply because the only way to ensure the player stops at an exact distance is to snap them to a position relative to the collided object: this approach would require you to be much more mindful of bounding boxes and origins though, so I would try simply raising the precision first.

edit: on top of this, you may need to resize your mask by 1 pixel horizontally - this will hopefully remove the overlap issue without introducing the 1-pixel-gap issue you had when you resized the mask before.
 

Bentley

Member
I put a draw event over the player to check the hsp and even when it was a whole number I still overlap a wall by one pixel. I tried rounding the hsp and vsp but I still kept running into the issue. Either I am not rounding in the right place or I'm really goofing up something bad. Thanks for the reply though, I appreciate it!
No problem. If you're rounding hspd/vspd right before you move, and you don't change you x/y position in other places, then you shouldn't be going into a wall. Are there other places that you're changing your x/y?
 
M

McFlyGold

Guest
No problem. If you're rounding hspd/vspd right before you move, and you don't change you x/y position in other places, then you shouldn't be going into a wall. Are there other places that you're changing your x/y?
I only have one other area where I am changing the x position, and that's with a teleporter. But it only operates up against a specific type of wall and only if you have collected the teleporter object. Otherwise as far as I know, no I am not changing either the x or y position of the player object.
 
M

McFlyGold

Guest
Code:
while(!place_meeting(x+sign(hsp), y, obj_wall)){ x += sign(hsp) };
Note here that sign() will return either 1 or -1, so the current state of the code only works on a per-pixel / whole number basis. You seem to have remedied the issue with your vertical collision check:

Code:
while(!place_meeting(x, y+(sign(vsp)/10), obj_wall)){ y += (sign(vsp)/10) };
Note the difference: for your horizontal, you're checking at position x+sign(hsp) , but for your vertical, you're checking at position y+(sign(vsp)/10). Because of the "/10" in there, your vertical check is accurate to .1 of a pixel. To make it accurate to 0.001, you would divide the sign by 1000 -- or just multiply by 0.001. Make this change to your horizontal and see if it helps. Don't make the precision too high though, because that increases the amount of times the while loop needs to run before stopping.

From a technical perspective, it won't be absolutely perfect, simply because the only way to ensure the player stops at an exact distance is to snap them to a position relative to the collided object: this approach would require you to be much more mindful of bounding boxes and origins though, so I would try simply raising the precision first.

edit: on top of this, you may need to resize your mask by 1 pixel horizontally - this will hopefully remove the overlap issue without introducing the 1-pixel-gap issue you had when you resized the mask before.
Okay, I will check on that when I get home tonight. I can say that I tried expanding the player objects mask out by one pixel on both the left and right sides, but that just kept me one pixel away from the wall instead of being flush with it. Thanks for your input! I appreciate it.
 
M

McFlyGold

Guest
I added a link to my first post showing a gif of the issue that I'm having. I couldn't add it before because I had less than five posts. :)
 
M

McFlyGold

Guest
So it seems like I may have fixed the issue.
Firstly I changed
Code:
while(!place_meeting(x, y+(sign(vsp)/10), obj_wall)){ y += (sign(vsp)/10) };
to just
Code:
while(!place_meeting(x, y+sign(vsp), obj_wall)){ y += sign(vsp) };
since it didn't seem to be making a difference in my vertical movement since I was still popping into the ground by 1 pixel. I also tried changing (sign(vsp)/10) to (sign(vsp)/1000) as was suggested for both the vsp and hsp, but that didn't help the 1 pixel overlap.

@Bentley suggested I try rounding my hsp and vsp, so I changed
Code:
x += hsp;
to
Code:
x += hsp;
x = round(x);
and did the same to vsp as well and that seems to be working now! The player object no longer seems to overlap neither the floor or the walls! I did have to modify the wall sliding code as for whatever reason beyond my knowledge, rounding the the x and y made it so the player got stuck to the walls instead of sliding down them. This only happened if you were ascending when you collided with the wall.

All in all, things seem to be working now and I'm very grateful for the help I received in the forums! Thank you!
 
Last edited by a moderator:

TheouAegis

Member
Hate to be the bearer of ill news:

x += hsp; x = round(x);
Suppose x is 16 and hsp is 0.4

round(16 + 0.4) = 16

In other words, any speed less than 0.5 will not move the player.

Suppose hsp is 0.6

round(16 + 0.6) = 17

In other words, any speed greater than or equal to 0.5 will be the same as any speed between 0.5 and 1.5.

You need that fractional offset preserved somewhere. Consider having in your Begin Step event:

x += xfrac;
y += yfrac;

Then just before your collision code have:

xfrac = x mod 1;
yfrac = y mod 1;
x -= xfrac;
y -= yfrac;
 
M

McFlyGold

Guest
Hate to be the bearer of ill news:
No no, please... bear me ill news! I want to get this right!

So where am I getting xfrac and yfrac from?

Here's the player's create event:
Code:
///@description Set Variables

if (!instance_exists(obj_game_controller))
{
    instance_create_layer(x, y, "Player", obj_game_controller);
}

//x_scale = 1.0;
//y_scale = 1.0;

startX = xstart;  //used for checkpoints
startY = ystart;  //used for checkpoints

hsp = 0; //Horizontal Speed
vsp = 0; //Vertical Speed

obj_player.layer = layer_get_id("Player"); //Make sure player is always on the right layer

// Movement Variables ///////////////////////////

master_spd = 1.5; //Master speed controller

grnd_acc = 0.15 * master_spd; //Ground acceleration
grnd_max = 4 * master_spd;    //Top ground speed

air_acc = 0.1 * master_spd;   //Air acceleration
air_fric = 0.01;              //Air Friction
air_max = 3.5 * master_spd;   //Top air speed

jump_spd = -3.0 * master_spd; //Jump height
jump_hold = false;            //Whether the player is holding the jump button
jump_timer = 20;              //Timer for jump height

grounded = false;             //Whether the player is on the ground
My controls script:
Code:
if(!grounded)// If we're in the air
{
    var acc = air_acc;
    var spd = air_max;
    var fric = air_fric;
}
else // If we're on the ground
{
    var acc = grnd_acc;
    var spd = grnd_max;
    var fric = obj_game_controller.fric;
}

var l_wall = place_meeting(x-1, y, obj_wall);// Wall on left?
var r_wall = place_meeting(x+1, y, obj_wall);// Wall on right?


// Movement //////////////////////////////////

if(key_left)
{
    hsp -= acc;
    if(hsp > 0.3){ hsp -= fric };
}

if(key_right)
{
    hsp += acc;
    if(hsp < -0.3){ hsp += fric };
}

if((!key_left and !key_right) or (key_left and key_right))
{
   
    if(hsp > 0.3){ hsp -= fric };
    else if(hsp < -0.3){ hsp += fric };
    else{ hsp = 0 };
}

if(hsp > spd){ hsp = spd };
if(hsp < -spd){ hsp = -spd};

// Jumping ///////////////////////////////////

if(key_jump and grounded)// Initial jump
{
    vsp = jump_spd; // Move up
    jump_timer -= 1;
    if (jump_timer == 0)
    {
        jump_hold = false;
        jump_timer = 20;
    }
    jump_hold = true;
}

if(key_jumpr){ jump_hold = false };

// Wall Jumping //////////////////////////////

if(!grounded and (l_wall or r_wall))
{
    if(key_jump) // Jumping off wall
    {
     
        if(l_wall)// Away left wall
        {
            hsp = jump_spd * -0.8;
            vsp = jump_spd * 0.7;
        }
        else if(r_wall)// Away right wall
        {
            hsp = jump_spd * 0.8;
            vsp = jump_spd * 0.7;
        }
    }
        jump_timer -= 1;
    if (jump_timer == 0)
    {
        jump_hold = false;
        jump_timer = 20;
    }
        jump_hold = true;
}
And Collision script:
Code:
// Moving Horizontally /////////////////////////////
if(place_meeting(x+hsp, y, obj_wall))
{
    while(!place_meeting(x + sign(hsp), y, obj_wall)){ x += sign(hsp) };
    hsp = 0;
}


// Apply speed after checks ////////////////////////

x += hsp;
x = round(x);

//scr_teleblur(c_white, room_speed * 0.25); //If you wanted a blur effect... this is where it goes!

// Moving Vertically ///////////////////////////////
if(place_meeting(x, y+vsp, obj_wall))
{
    while(!place_meeting(x, y+sign(vsp), obj_wall)){ y += sign(vsp) };
    vsp = 0;
   
    if(place_meeting(x, y+1, obj_wall))// Squish when landing
    {
        grounded = true;
       
        //x_scale = 1.25;
        //y_scale = 0.75;
    }
   
}

// Apply speed after checks ////////////////////////////
y += vsp;
y = round(y);
I pretty much got most of the code from three videos by Lewis Clark. The first one where he sets up most of the variables and such is here:

I also appreciate you checking in even after I marked this thread as [REOLVED]... I guess I'll edit the title for now. ;)
 

TheouAegis

Member
You set xfrac and yfrac to 0 in the Create events so they are recognized later.

xfrac is just (x mod 1) and yfrac is just (y mod 1). So if your x is 14.336, xfrac will be .336 and if you subtract that from x, x will now be 14. On the next step before you do any movement calculations, you add xfrac back to x so you don't lose that fractional value in your normal movement calculations.

So add xfrac to x, update your speeds and add them to x, update xfrac and subtract it from x to round off x, perform your collisions and drawings with no rounding errors.
 
M

McFlyGold

Guest
You set xfrac and yfrac to 0 in the Create events so they are recognized later.

xfrac is just (x mod 1) and yfrac is just (y mod 1). So if your x is 14.336, xfrac will be .336 and if you subtract that from x, x will now be 14. On the next step before you do any movement calculations, you add xfrac back to x so you don't lose that fractional value in your normal movement calculations.

So add xfrac to x, update your speeds and add them to x, update xfrac and subtract it from x to round off x, perform your collisions and drawings with no rounding errors.
I'm really upset with myself because I'm trying to wrap my head around what you're telling me and it's just not computing! It's actually pretty frustrating because I imagine that what you're explaining to me is probably a very simple concept, but I can not for the life of me wrap my brain around it. I understand this:
xfrac is just (x mod 1) and yfrac is just (y mod 1). So if your x is 14.336, xfrac will be .336 and if you subtract that from x, x will now be 14.
That's fine. I get that. The next line where you say:
So add xfrac to x, update your speeds and add them to x, update xfrac and subtract it from x to round off x, perform your collisions and drawings with no rounding errors.
is where I'm getting lost. My understanding was that my movement speed was being determined in the collision code. Or at least, that's where "x" is getting it's speed from. So that's where I'm lost. I'm sorry. I'm grateful that you're taking the time to explain things to me, and I feel like I'm too daft to understand where things are going wrong. :(
 
M

McFlyGold

Guest
So I've been searching for a solution to this and I'm beginning to see why I found the solution confusing. I believe I found an older tutorial that explains the concept of what @TheouAegis was trying to explain to me. It's by Zack Bell and can be found here: https://zackbellgames.com/2014/10/29/understanding-collision-basics-pt-2/

My main problem was understanding:
x += xfrac;
y += yfrac;
since my x and y position was being determined after my collision code, I didn't understand why this would go before my collision code if neither x nor y had been assigned a value yet! Using Zack's example, it looks like x represented hsp and y represented vsp. So that started to make things click a bit better for me!

I've since modified my code and have once again seem to have solved the issue.
Thanks to @TheouAegis for setting me down the right path to getting this issue sorted out! I appreciate it!
 
P

polarizeme

Guest
I spent the whole weekend looking for someone else having this issue and didn't stumble upon this until after I'd already posted my issue. Thanks so much to both of you for this fix; while I've never used the aforementioned tutorial, I was able to retrofit this discussed fix into my code.

Cheers!
 

Bentley

Member
Hate to be the bearer of ill news:



Suppose x is 16 and hsp is 0.4

round(16 + 0.4) = 16

In other words, any speed less than 0.5 will not move the player.

Suppose hsp is 0.6

round(16 + 0.6) = 17

In other words, any speed greater than or equal to 0.5 will be the same as any speed between 0.5 and 1.5.

You need that fractional offset preserved somewhere. Consider having in your Begin Step event:

x += xfrac;
y += yfrac;

Then just before your collision code have:

xfrac = x mod 1;
yfrac = y mod 1;
x -= xfrac;
y -= yfrac;
Just a quick question. I'm trying to learn how to add this in myself (hope you see this post):

Let's say you're moving right and you accumulate an xfrac of 0.8.
Then you start moving left.
Won't it take longer to get into the negatives than normal?
And if that's the case, how would you deal with that? Simply set xfrac to 0 when there's a horizontal
direction change?

Either way, thanks for the posts, been learning from them.
 

TheouAegis

Member
xfrac has nothing to do with your speed, it only has to do with positioning. The code I posted actually rounds off toward zero. If the player is at a positive coordinate, then the coordinate will be rounded down toward zero, resulting and coordinate behavior like in console games. If the player is at a negative coordinate, then the coordinate will be rounded up toward zero (which is more logical if you allow your brain to think about negative coordinates in terms of a mirrored world). But as I said, it has nothing to do with speeds. I'm not adding to the fraction, which is what they would have done on old console games. All I am doing in the code is simply taking the fraction away from the coordinate, performing any routines that prefer integral coordinates, then adding the fraction back in so that when I apply speeds I am moving to the proper position.

If x is 8.4, hspd is -0.8, and there is an 8x8 block at x=0. The code would drop X down to the next integer towards zero, which is 8. Using a popular pre-emptive collision check, 8-0.8 would result in a collision, but since 8+sign(0.8) results in a collision the position never changes. What this means for my xfrac code is that at the start of the next step that 0.4 will be added back on so x would be 8.4, not 8.0 as it should have been, so if the player moves right with hspeed 0.8 on the next step, he'd move to 9.2 rather than 8.8 as one would have expected. This is a minor oversight. However, if x is 8.4 and hsp is +0.8 and there is a wall at 9, then it would result in 8.4+0.8 being "collision free" and then on the next step there'd be a 1 pixel overlap... Maybe. That's one drawback I could foresee.
 
Top