• Hey! Guest! The 37th GMC Jam will take place between May 28th, 12:00 UTC and June 1st, 12:00 UTC. Why not join in! Click here to find out more!

GML Pixel-Perfect Object-based Collision

GM Version: All
Target Platform: All
Downloads: (Towards bottom of post)

Summary:
This post is for anyone who needs help with collision checking when it comes to pixel- and subpixel-perfect accuracy. This isn't grid collision but I feel it is a more efficient way of implementing object-based collisions.

Tutorial:
So I guess it's safe to assume that nearly any game dev using GM is familiar with how object-based collisions are commonly handled...

Code:
//Horizontal collisions
if place_meeting(x+hspd,y,obj_wall) {
        while !place_meeting(x+sign(hspd),y,obj_wall) {
                 x += sign(hspd);
        }
        hspd = 0;
}
x += hspd;
...then check for vertical collisions the same way.

This is all fine and dandy and certainly is a way to check for collisions. However, when working with a low-res project (very low, like 144x112) or even prototypes, you will begin to notice that if the player is moving at subpixel increments (so like if it's movespeed contains a decimal), the player is just a tad bit over/under the platform he is colliding with. While this collision checking system works, it isn't exactly pixel-perfect.

The player is an 8x8 blue square. I set the player's movespeed to 2.9. If you look close enough, you can see that he goes a little bit over the wall on the left and actually never touches the wall on the bottom...

So how can we fix this? Well rather than checking from the player's speed, we check from the player's location. If you are familiar with how tile-based collisions work, then you should understand what I mean by this. (To put it simply, tile-based collisions work using a grid system. If the player runs into a tile, snap him back to the grid. I recommend doing some research on how tile-based collision checking works if you are having trouble understanding.) We use the player's collision mask directly to determine whether or not we're about to collide with the wall (because you know, it's a collision mask).

First, we need to get the player's bounding box sides based on his origin. To do this, simply...

Code:
//Offsets
var sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
If you've ever seen @GMWolf's video on GM2 tile-collisions, then this should look familiar. This will tell us how far the player's bbox_top is from the origin and will be useful to re-aligning the player later on. Now we do this for the other 3 sides: bottom, left, and right, using sprite_get_xoffset for both left and right.

NOTE: If the collision mask is not the same as the sprite's (say the player has animations and is using another sprite as a consistent collision mask), switch sprite_index to mask_index that way the mask isn't changing between each animation (or change the collision mask in the object editor, whichever works)

So now that we've got our sprite info, it's time to put it to good use. We'll start with horizontal collisions first...

Code:
//Horizontal collisions
x += hspd;

//Snap
if place_meeting(x+sign(hspd),y,obj_wall) {
    var wall = instance_place(x+sign(hspd),y,obj_wall);
    if hspd > 0 { //right
        x = (wall.bbox_left-1)-sprite_bbox_right;
    } else if hspd < 0 { //left
        x = (wall.bbox_right+1)-sprite_bbox_left;
    }
    hspd = 0;
}
Now I know this may seem complicated at first glance, but logically it isn't. First, we check to see if there is a wall 1 pixel in front of us in relation to our hspd. We then get the id of the wall we're about to collide with, and snap the player in relation to the wall, so if we're moving right and we hit a wall from our right, we use the bbox_left from the wall (minus 1 because we don't want to be exactly where the wall's bbox_left is, we want to be next to it.), subtract it from the player's right offset, then set the player's hspd to 0. Then we do the same vertically. This way the player isn't over/under the wall anymore; the player is exactly where he needs to be. This is pixel-perfect.

Now the player is touching both walls without going over them or ever touching them at all. The player's movespeed is still 2.9.

Really that's all there is to it. Cram all this into a script and it should be good to go!

NOTE: This tutorial does NOT take diagonal collisions into account currently, but I will update the thread once I can implement it.

If anyone needs a more "hands-on" example, I created a project to demonstrate*:
GM2: https://drive.google.com/open?id=0B5Yl_iO_QkgZNklac0UyRllIRHc
GM:S/1.4: https://drive.google.com/open?id=0B5Yl_iO_QkgZWGNFZWxLdl9oQ2c

Links: N/A

*press [SPACE] to switch between collision checking systems
 
Last edited:
S

Snayff

Guest
Thanks for sharing this.

Please forgive my ignorance but what is the benefit of using code for collision checking over the collision event?
 
Please forgive my ignorance but what is the benefit of using code for collision checking over the collision event?
Ignorance forgiven. :p

The collision event only triggers when two collision masks meet. When using code for collision checking in the step event, it's triggered every frame. It's better to check for collisions before actually colliding with something so that the player has a better idea of when to stop moving. At least, this is the logic behind using the commonly coded collision system. My optimized collision system simply relocates the player when a collision does happen, but since we have to check the player's hspd beforehand, it must be coded in. That and using code is (generally) more faster & efficient overall. You have more control over using code than you do using events, given the situation.
 
Last edited:
S

Snayff

Guest
...using code is (generally) more faster & efficient overall.
Haha thanks, and thanks for coming back to me mate.

All things being equal, does that axiom hold for all events? I.e. at a certain point do you believe it is worth shifting the work on to custom functions?

At my current stage of GML infancy I think sticking with the native events makes sense but I expect as I progress I will run in to these hurdles so it is good to know about these things ahead of time!

P.s. the GIFs really helped clarify! the extra effort in including them is definitely appreciated!
 
do you believe it is worth shifting the work on to custom functions?
Indeed I do, but it's whatever works for you. I personally have never used the D&D system so I can't say GML is better, but hey if it works it works. When using GML though, you have more control over how and when things should happen based on what your game requires. Working with GML takes some getting used to at first, but it becomes easier with experience. I've reached a point where it feels "unnatural" to not work with code because I've grown so attached to it. I definitely recommend diving into GML a little more so you can have a better understanding of how everything works in GM and better understand why something isn't working right from a logical standpoint and attempt to fix it. I'm not saying you can't accomplish this with D&D, but with GML it's more "system-integrated" if that makes any sense.

P.s. the GIFs really helped clarify! the extra effort in including them is definitely appreciated!
Your welcome! ;)
 

Edmanbosch

Member
Pretty good, nice tutorial. Your code is a bit messy though. Also, would it be better to put the snap code inside the actual wall collision?
 

Edmanbosch

Member
I'm afraid not, since we must check ahead of the player before the collision, rather than when a collision is triggered. We're not relocating the player when we collide, but before we collide.
I've tried putting it in the collision code, and it works just fine. I'm still not sure what the problem of doing this is?
 
I've tried putting it in the collision code, and it works just fine. I'm still not sure what the problem of doing this is?
How did you go about doing it exactly? Did you mean making a collision event with the wall inside the player? I tried doing that and it doesn't seem to be working for me. Perhaps you left the code in collide_2()?
 

Edmanbosch

Member
How did you go about doing it exactly? Did you mean making a collision event with the wall inside the player? I tried doing that and it doesn't seem to be working for me. Perhaps you left the code in collide_2()?
This was how I did it:
Code:
if (place_meeting(x + hsp, y, obj_wall)) {
    while (!place_meeting(x + sign(hsp), y, obj_wall)) {
        x += sign(hsp);
    }
 
    _wall = instance_place(x + sign(hsp), y, obj_wall);
    if (hsp > 0) {
        x = (_wall.bbox_left - 1) - _sprite_bbox_right;
        hsp = 0;
    }
    else {
        x = (_wall.bbox_right + 1) - _sprite_bbox_left;
        hsp = 0;
    }
 
    hsp = 0;
}
x += hsp;
And I did the same for the vertical collision.
 
This was how I did it:
Code:
if (place_meeting(x + hsp, y, obj_wall)) {
while (!place_meeting(x + sign(hsp), y, obj_wall)) {
x += sign(hsp);
}

_wall = instance_place(x + sign(hsp), y, obj_wall);
if (hsp > 0) {
x = (_wall.bbox_left - 1) - _sprite_bbox_right;
hsp = 0;
}
else {
x = (_wall.bbox_right + 1) - _sprite_bbox_left;
hsp = 0;
}

hsp = 0;
}
x += hsp;
And I did the same for the vertical collision.
Oookay I see what you did there, and hey that's one way to do it. To me it seems like extra work though, but I admit it is a bit easier to read than what I came up with.
 
How is it extra work?
I don't find it necessary to combine both collision systems honestly. I can see that one could work as a fail-safe for the other, but what I had looked at when I said it is when I saw this code:
while (!place_meeting(x + sign(hsp), y, obj_wall)) { x += sign(hsp); }
...
hsp = 0;
It's redundant to inch closer to the wall when the player is going to be relocated by the code that comes after it anyways, and hsp is already going to be 0 if we are next to the wall, so there's no need to set it to 0 at the end. Also, I keep getting this error:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object Player:

Variable <unknown_object>.bbox_right(21, -2147483648) not set before reading it.
at gml_Script_collide_2 (line 19) - x = (wall.bbox_right + 1) - sprite_bbox_left;
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_collide_2 (line 19)
called from - gml_Object_Player_Step_0 (line 17) - collide_2();
What I believe is happening here is var "wall" is being set when the player detects a wall "hspd" pixels in front of them. Since var "wall" = instance_place(x+sign(hspd),y,obj_wall), the player detects a wall, but "wall" doesn't. This leads to the code trying to find the bbox_right of an object that isn't detected yet. Am I the only one getting this error?

Regardless, here's what I believe to be a better solution (and I'll update the thread after this post):
Code:
//Horizontal collisions
x += hspd;
if place_meeting(x+sign(hspd),y,Solid) {
    var wall = instance_place(x+sign(hspd),y,Solid);
    if hspd > 0 { //right
        x = (wall.bbox_left-1)-sprite_bbox_right;
    } else { //left
        x = (wall.bbox_right+1)-sprite_bbox_left;
    }
    hspd = 0;
}
...and the same vertically.
 
Last edited:

Edmanbosch

Member
I prefer checking to see if the player is about to hit a wall and add hsp to the x after collisions(same for vertical), but yours works too.
 
S

Snayff

Guest
So I am using GML. I haven't used dnd at all. I am using a collision event and then putting the code in there. Am I right in thinking that your suggested method would be to create your own script to check for collision? Which presumably would be stored in a step event?
 
Am I right in thinking that your suggested method would be to create your own script to check for collision? Which presumably would be stored in a step event?
That'd be the way to do it! If the code is put into a script, it helps keep things organized and can be sometimes easier to debug (because you'll be able to find bugs faster when errors do occur.)

And yes, we want this in the step event because in addition to collision checking, we are also moving the player per frame, as suggested by the code "x+=hspd" and "y+=vspd" (and also because we can't really put it anywhere else in all honesty).
 
S

Snayff

Guest
I am trying to implement this but I am being dense... what is the Solid variable being looked for? Does it require the "solid" option to be ticked on walls?

Edit:
Changed it to look for the parent of my walls and it seems to work! Need to test properly but looking good, thanks buddy!
 
Last edited by a moderator:
M

MightyJo

Guest
Mate you might just wanna change this:
Code:
var sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
And instead of using sprite_index tell the reader to use his mask image.

Some of us use animations, and using sprite_index there, makes my player spaz like there's no tomorrow. It gave me quite a few head scratches before I figured out what was going on. Otherwise brilliant code my friend. Kisses and smooches all around.
 
M

MightyJo

Guest
The mask is always the same. Your code gets the boundaries of the current sprite that is shown, not the boundaries of the mask. The sprite of the player is ever changing in width:



When my player is in that state and tries to collide with the wall he starts spazzing in front of it (also, just so you know, the animation is set to play only when x is either > or < than xprevious and to show a static sprite when they are =). Or maybe the problem is that the player's mask is one pixel smaller on each side (left and right) that the smallest animation sprite:



But I don't think that's the problem since your code only takes into account sprite boundaries, I think, right? But regardless of what the issue really is, changing sprite_index to mask_index solved the problem.
 
Last edited by a moderator:
But I don't think that's the problem since your code only takes into account sprite boundaries, I think, right? But regardless of what the issue really is, changing sprite_index to mask_index solved the problem.
The sprite_get_bbox functions get the bounding box sides relative to (0,0) of the sprite index. They take into account mask boundaries, obtained from the sprite itself. One thing to note is that these values are taken from the collision mask properties window of the sprite editor...
So according to this, sprite_get_bbox_top would return 0 here. If you're using a separate sprite as a collision box, maybe try replacing sprite_index with the index of the collision sprite? I replaced sprite_index with mask_index like you said and the player keeps going into the wall. That's an interesting error though...because I'm using this same collision system in another project that uses animations as well and I've never gotten this error. But hey if it works for you, it should be okay I guess.
 

Ceddil

Member
I tried this and it jumps through the wall when going at it from the top or bottom. could you help?
Code:
//bbox variables
var sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
var sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
var sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
var sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);

//Horizontal collisions
x += hspd;

//Snap
if place_meeting(x+sign(hspd),y,obj_solid) {
    var wall = instance_place(x+sign(hspd),y,obj_solid);
    if hspd > 0 { //right
        x = (wall.bbox_left-1)-sprite_bbox_right;
    } else { //left
        x = (wall.bbox_right+1)-sprite_bbox_left;
    }
    hspd = 0;
}

//vertical collisions
y += vspd;

//Snap
if place_meeting(x,y+sign(vspd),obj_solid) {
    var wall = instance_place(x,y+sign(vspd),obj_solid);
    if vspd > 0 { //top
        y = (wall.bbox_top-1)-sprite_bbox_top;
    } else { //bottom
        y = (wall.bbox_top+1)-sprite_bbox_bottom;
    }
    vspd = 0;
}
[CODE]
 
Last edited:
Code:
//Snap
if place_meeting(x,y+sign(vspd),obj_solid) {
    var wall2 = instance_place(x,y+sign(vspd),obj_solid);
    if vspd > 0 { //top
        y = (wall2.bbox_top-1)-sprite_bbox_top;
    } else { //bottom
        y = (wall2.bbox_top+1)-sprite_bbox_bottom;
    }
    vspd = 0;
}
When vspd > 0 (meaning you're going down), you should be subtracting by sprite_bbox_bottom instead of sprite_bbox_top, because the bottom of the player should meet the top of the wall. As for when vspd < 0 (going up), it should be the same logic: The top of the player should be colliding with the bottom of the wall. It's important to not understand how something isn't working, but why it isn't. Doing so can strengthen your efficiency as a programmer so you can fix these kinds of errors on your own later on. Hope you got something out of this!
 
Last edited:

Ceddil

Member
When vspd > 0 (meaning you're going down), you should be subtracting by sprite_bbox_bottom instead of sprite_bbox_top, because the bottom of the player should meet the top of the wall. As for when vspd < 0 (going up), it should be the same logic: The top of the player should be colliding with the bottom of the wall. It's important to not understand how something isn't working, but why it isn't. Doing so can strengthen your efficiency as a programmer so you can fix these kinds of errors on your own later on. Hope you got something out of this!
i understand why it isnt working, but i cant fix it. I dont know what i am doing wrong here. I re-wrote the code and it is working a little better, but i still need some help
Code:
//vertical collisions
y += vspd;

//Snap
if place_meeting(x,y+sign(vspd),obj_solid) {
    var wall = instance_place(x,y+sign(vspd),obj_solid);
    if vspd > 0 { //top
        y = (wall.bbox_top+1)-sprite_bbox_bottom;
    } else { //bottom
        y = (wall.bbox_bottom-1)-sprite_bbox_top;
    }
    vspd = 0;
}
this is in the step event of the player, and when i collide vertically it pushes me all he way to the right.
 
when i collide vertically it pushes me all he way to the right.
Because you're putting the player inside the wall. Next to wall.bbox_top and wall.bbox_bottom, flip the signs. It should be (wall.bbox_top-1), because we want to be a pixel above the top. Another thing to note is in GameMaker (or any other display for that matter), coordinate (0,0) is at the top left, not the bottom left like a standard graph. So if we go up, we are decreasing in y, but if we go down, we are increasing. It's weird, but that's just how it is.

So it would be (wall.bbox_top-1) and (wall.bbox_bottom+1).
 

Ceddil

Member
Because you're putting the player inside the wall. Next to wall.bbox_top and wall.bbox_bottom, flip the signs. It should be (wall.bbox_top-1), because we want to be a pixel above the top. Another thing to note is in GameMaker (or any other display for that matter), coordinate (0,0) is at the top left, not the bottom left like a standard graph. So if we go up, we are decreasing in y, but if we go down, we are increasing. It's weird, but that's just how it is.

So it would be (wall.bbox_top-1) and (wall.bbox_bottom+1).
i did exactly what you said, and i dont know if i'm just bad at coding or math, but it didnt change anything.
Code:
//vertical collisions
y += vspd;

//Snap
if place_meeting(x,y+sign(vspd),obj_solid) {
    var wall = instance_place(x,y+sign(vspd),obj_solid);
    if vspd > 0 { //top
        y = (wall.bbox_top-1)-sprite_bbox_bottom;
    } else { //bottom
        y = (wall.bbox_bottom+1)-sprite_bbox_top;
    }
    vspd = 0;
}
and if this is the problem, here is the bounding box variables
Code:
//bbox variables
var sprite_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index);
var sprite_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index);
var sprite_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index);
var sprite_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index);
 
i did exactly what you said, and i dont know if i'm just bad at coding or math, but it didnt change anything.
You didn't change your horizontal collisions at all? The vertical collisions are right as well as the bounding boxes, so it should be functional. If nothing else works, try switching "sprite_index" to "mask_index" in the bounding box vars. This seemed to work for @MightyJo so it may work for you. Also be sure your player has a mask as well as your walls.
 

Mister10

Member
hi, I tried the code, and my character just falls through the floor. I used the whole mask index thing and it didn't fix it.
here's my code. I don't know if I'm doing anything wrong.

//bbox
var sprite_bbox_top = sprite_get_bbox_top(mask_index) - sprite_get_yoffset(mask_index);
var sprite_bbox_left = sprite_get_bbox_top(mask_index) - sprite_get_xoffset(mask_index);
var sprite_bbox_right = sprite_get_bbox_top(mask_index) - sprite_get_xoffset(mask_index);
var sprite_bbox_bottom = sprite_get_bbox_top(mask_index) - sprite_get_yoffset(mask_index);

//Horizontal collisions
x += hsp;

//Snap
if place_meeting(x+sign(hsp),y,obj_block) {
var wall = instance_place(x+sign(hsp),y,obj_block);
if hsp > 0 { //right
x = (wall.bbox_left-1)-sprite_bbox_right;
} else { //left
x = (wall.bbox_right+1)-sprite_bbox_left;
}
hsp = 0;
}
//Vertical collisions
y += vsp;

//Snap
if place_meeting(x,y+sign(vsp),obj_block) {
var wall = instance_place(x,y+sign(vsp),obj_block);
if vsp > 0 { //right
y = (wall.bbox_top-1)-sprite_bbox_bottom;
} else { //left
y = (wall.bbox_bottom+1)-sprite_bbox_top;
}
vsp = 0;
}
 

Bentley

Member
Thanks for the tutorial, it was very helpful. Thumbs up!

I just had a quick question.
First, we need to get the player's bounding box sides based on his origin. (The player doesn't need to be centered, but it is strongly recommended.)
Why do you recommend a centered origin as the code works for any origin (correct me if I'm wrong).
 
I put all of the code in the step event of my player but when I land on obj_floor I glitch inside of it and start moving really fast to the right until there isnt any floor and then just fall.
 
R

ridiculoid

Guest
For anybody having trouble with this. There's a bug in this code. You need to change the "else" statements to "else if" statements. The "else" statements will trigger when the vspd or hspd is == 0. You're not going to want that. So instead of "else { //left", it needs to be "else if hsp < 0 { //left"
 

NeZvers

Member
Lovely tutorial. I was frustrated with traditional collision system myself and this is similar to one of my collision test systems, only bit better.
I feel that this is a bit better way to do since you don't need loop to do counting.
 

NeZvers

Member
Ok, I got time to revise my game code and I wanted to implement this collision system aaaand with that I found some improvements.
Since I don't like incomplete code shown, I'll lay it down so newcomers couldn't mess it up.
First thing - since I use same collision mask for all animations I SET IT AND FORGET IT in create event:

Code:
///Create_event

//I think this is better calculation for single mask
sprite_bbox_top = bbox_top - y;
sprite_bbox_bottom = bbox_bottom - y;
sprite_bbox_right = bbox_right - x;
sprite_bbox_left = bbox_left - x;
Code:
//Horizontal collisions
x += hspd;

//Snap
if place_meeting(x, y, oSolid) { // DON'T USE sign(hspd) since you are checking current possition
    var wall = instance_place(x+sign(hspd),y,oSolid); // same thing you are inside and don't want to check 1px more
    if (hspd > 0) { //right
        x = (wall.bbox_left-1)-sprite_bbox_right;
    } else if (hspd < 0) { // <<------ as it was pointed out need to have else if otherwise hspd=0 is included
        x = (wall.bbox_right+1)-sprite_bbox_left;
    }
    hspd = 0;
}

//Vertical collisions
y += vspd;

//Snap
if place_meeting(x, y, oSolid) {
    var wall = instance_place(x, y, oSolid);
    if (vspd > 0) { //down
        y = (wall.bbox_top-1) - sprite_bbox_bottom;
    } else if (vspd < 0) { //up
        y = (wall.bbox_bottom+1) - sprite_bbox_top;
    }
    vspd = 0;
}
This is now my go-to collision system because it survived vertically moving platforms spectacularly.
 
Last edited:
Woah this thread has gotten a lot of views! Much appreciation to those who have found bugs in the code, and I'll update the thread along with those fixes as well as the aforementioned diagonal capabilities. Also a few things:
Why do you recommend a centered origin as the code works for any origin (correct me if I'm wrong).
I guess it shouldn't matter too much, but I found it to be less buggy that way. It's a habit of mine that sorta bled through the thread so I apologize if this caused any confusion.
You need to change the "else" statements to "else if" statements. The "else" statements will trigger when the vspd or hspd is == 0. You're not going to want that. So instead of "else { //left", it needs to be "else if hsp < 0 { //left"
Thanks for this, I'll update the thread!
The mask is always the same. Your code gets the boundaries of the current sprite that is shown, not the boundaries of the mask.
Bit of an old quote but this is true. Didn't find the bug out myself until just recently, so expect an update!
 
C

CarlFoReal

Guest
I used this code and got an error saying the vspd was not set prior to reading it. chances are I'm just missing a variable but who knows. Any help would be appreciated given I've got minimal experience coding and collisions are the current problem. Thanks in advance.
 

Jumper

Member
This code is awesome, works great for moving platforms. But my guy starts teleporting all over the place if he collides both horisontal and vertically. Is there any good workaround for that?

And to OP: Why do you (and nearly everyone else) post half the code and "Then we do the same vertically", and your demo project is an .exe and not a project file. This was easy enough code but man, everyone does that, its so annoying to spend 20 minutes of plotting and correcting code just to see if it works or not... Thanks for posting everything @NeZvers
 
Shouldn't this way also include a while loop as well for the case that there are 2 (non player) colliders intersecting each other?

2019-01-11 14_39_50-Unbenannt - Paint.png

Without the loop it highly depends on Game Maker which of the Colliders gets detected, and in case it would be the wrong one it would snap the player inside the other one.
Also the loop would not increase performance to much because these cases are rather rare and only would loop in such cases.

Or do I understand the snippet wrong?
 
C

Cedric_of_Maroon

Guest
I was able to get pixel-perfect collisions by using the simpler method (judging based on speed rather than snapping) by just rounding the x and y coordinates after the collision every step:

x = round(x);
y = round(y);

Is there any downside to this? Seems to work fine.
 

Joe Ellis

Member
This is really good, and I hope people see this and think its the normal way to do it, rather than the stupid while loop,
The bbox vars are underrated, most simple collisions can be done with these and pretty much every old 2d game used to work like this.

You need to learn about vectors for the diagonal collisions though, there's quite a few simple things you can do which make the objects slide along lines perfectly. Learn about vertices, edge vectors between 2 vertices, cross products & dot products
 
Top