How do I get the exact position of a collision in GMS 1.4?

T

Thadd

Guest
The questions been boggling me for quite some time and I'd like to know how do I get the exact position at which a collision happens from one object to another.
For example: (a 2D game)
ObjectA meets with ObjectB and blood appears. Much like you shoot something in real life and the blood or dust appear from the place you shot into it. They are two large objects so just using their x and y positions won't work. Ive tried making a two function method where there's a "get_collision_x(who)" and a "get_collision_y(who)" but none work. Any help?
 

samspade

Member
The questions been boggling me for quite some time and I'd like to know how do I get the exact position at which a collision happens from one object to another.
For example: (a 2D game)
ObjectA meets with ObjectB and blood appears. Much like you shoot something in real life and the blood or dust appear from the place you shot into it. They are two large objects so just using their x and y positions won't work. Ive tried making a two function method where there's a "get_collision_x(who)" and a "get_collision_y(who)" but none work. Any help?
The short answer is there isn't any way to do this that is easy. You might think that because the game detects collisions that it knows where the collision is happening but that isn't true. At least not with any of the built in collision functions. The simplest explanation is this. Imagine two circles, each with a diameter of 10. If you measure the distance between the center of the two circles and the distance is more than 20, then you know the circles are not colliding. If you measure the distance and the distance is less than 20, then you know the circles are colliding, but you have no idea where. While it works slightly different with square collisions the idea is the same. The game is merely comparing distances and bounding boxes it doesn't know where the collision is occurring. This by the way is true for most games. In other words it isn't a game maker thing, it is a games in general thing. Pixel perfect collision detection would take too much processing power.

So there are a couple ways to get close to what you want. First, is to use smaller hit boxes. If you check for a collision with a box that is 5x5 rather than 32x32 it will look reasonably close. Second, for each type of collision figure out the best way to guess. For example, with two circles if you know there is a collision you can easily get the distance between their two circles and then cut that distance in half and use that as the contact point. With two squares it would probably look reasonably good to do the same. Rectangles would be harder.
 
T

Thadd

Guest
The short answer is there isn't any way to do this that is easy. You might think that because the game detects collisions that it knows where the collision is happening but that isn't true. At least not with any of the built in collision functions. The simplest explanation is this. Imagine two circles, each with a diameter of 10. If you measure the distance between the center of the two circles and the distance is more than 20, then you know the circles are not colliding. If you measure the distance and the distance is less than 20, then you know the circles are colliding, but you have no idea where. While it works slightly different with square collisions the idea is the same. The game is merely comparing distances and bounding boxes it doesn't know where the collision is occurring. This by the way is true for most games. In other words it isn't a game maker thing, it is a games in general thing. Pixel perfect collision detection would take too much processing power.

So there are a couple ways to get close to what you want. First, is to use smaller hit boxes. If you check for a collision with a box that is 5x5 rather than 32x32 it will look reasonably close. Second, for each type of collision figure out the best way to guess. For example, with two circles if you know there is a collision you can easily get the distance between their two circles and then cut that distance in half and use that as the contact point. With two squares it would probably look reasonably good to do the same. Rectangles would be harder.
Thanks for that. But, yes, I've tried that. If this is what you mean. But I'm not sure why it lagged last time.
Edit: your reply raises some valid points. And I figured the precise collision checking was just an illusion. As I am no beginner. I've been messing with game maker for 7 years now (and it's an affair I'm usually proud of [usually]). Also, I have tried checking every pixel for a collision and that lagged. But the image I sent wasn't that example code it was another trial. I remember game-editor had perfect collisions. Just didn't have rotating objects. Anyway, I just learned what blending modes were today so maybe I'm still wet behind the ears. Lol sleep needed...
 

Attachments

Last edited by a moderator:

CMAllen

Member
If you know what you're colliding with, you'd use an iterative collision testing method. If there's collision, check again at half the previous distance to the colliding object. If there's a collision again, cut the collision distance check in half once more. If there isn't a collision, check again at half the difference between the current collision distance and the previous collision distance. It takes surprisingly few iterations of this to arrive at the exact distance the collision occurs at.
 

Bentley

Member
Why not use a loop and move 1 pixel at time. Every iteration, you check for a collision, and if there's a collision you store the position.
Maybe this is no good, but I think it would get you the correct X position of when object A first overlaps with object B. You could do the same for the Y position:
Code:
repeat (abs(hspd))
{
    if (!place_meeting(x + sign(hspd), y, obj_something))
    {
         x += sign(hspd); 
    }
    else
    {
        var side_that_collided = (hspd > 0 ? bbox_right : bbox_left);
        pos_of_collision_x = side_that_collided + sign(hspd);

        hspd = 0; 
        break; 
    }
}
 
Last edited:

TheouAegis

Member
One idea: Projectiles travel along a tangent (very loosely - a straight line). At the time of a collision, the projectile's tangent crossed one of the sides of the object it collided with. If bullet.y > object.bbox_top and bullet.yprevious < object.bbox_top, then you know the bullet's tangent likely crossed the object's upper side. You can take the slope of the tangent of the bullet and pass the bbox_top value of the object into the function which defines that slope to get the x-coordinate (the y-coordinate would be object.bbox_top).
 
T

Thadd

Guest
Can you describe the things that are colliding in more detail... maybe there is an easier solution that will work for your particular case.
A bullet with a wall type object. Making sparks fly from a direct position from where it collided.

I also had ran into this problem and checked out gmlscripts.com and found this helpfull script, maybe this can help you. thanks to @icuurd12b42 for making it!
Not quite what I was looking for. How exactly will this return the collided position of these objects? bje thanks.

Why not use a loop and move 1 pixel at time. Every iteration, you check for a collision, and if there's a collision you store the position.
Maybe this is no good, but I think it would get you the correct X position of when object A first overlaps with object B. You could do the same for the Y position:
Code:
repeat (abs(hspd))
{
    if (!place_meeting(x + sign(hspd), y, obj_something))
    {
         x += sign(hspd);
    }
    else
    {
        var side_that_collided = (hspd > 0 ? bbox_right : bbox_left);
        pos_of_collision_x = side_that_collided + sign(hspd);

        hspd = 0;
        break;
    }
}
Well an object is flying from an aimed direction of the players choosing (bullet) and collides with a ground/wall. This is an idea but sure won't work with a free moving projectile like a bullet... Thanks tho

If you know what you're colliding with, you'd use an iterative collision testing method. If there's collision, check again at half the previous distance to the colliding object. If there's a collision again, cut the collision distance check in half once more. If there isn't a collision, check again at half the difference between the current collision distance and the previous collision distance. It takes surprisingly few iterations of this to arrive at the exact distance the collision occurs at.
Sounds promising...but can you give an example of this?

Why not use a loop and move 1 pixel at time. Every iteration, you check for a collision, and if there's a collision you store the position.
Maybe this is no good, but I think it would get you the correct X position of when object A first overlaps with object B. You could do the same for the Y position:
Code:
repeat (abs(hspd))
{
    if (!place_meeting(x + sign(hspd), y, obj_something))
    {
         x += sign(hspd);
    }
    else
    {
        var side_that_collided = (hspd > 0 ? bbox_right : bbox_left);
        pos_of_collision_x = side_that_collided + sign(hspd);

        hspd = 0;
        break;
    }
}
Wait I just rubbed my eyes and saw what you meant. Will try this when I get home asap.
 

jaydee

Member
If your wall is an axis aligned square or rectangular object, you can easily determine the position of collision assuming you know two points of the projectiles path, and that path is a straight line.

Can still be done with an Orientated object too, but requires more math. If your path is not straight you have to adjust the line formula to the shape of the curve (parabolic most likely).

So you would save the original spawn point of the projectile OriginX, OriginY.

You can then work out the gradient of the path of the projectile, and the y intersect to give you the standard linear line equation in the form: y = mx + c, or rearranged form: x = (y - c) / m.
m = (projectile.OriginY - projectile.y)/ (projectile.OriginX - projectile.x);
c = OriginY - m * OriginX;

From here you can find at what point on any of the 4 sides it intersects, by using the follow line equations:
Y = bbox_top
Y = bbox_bottom
X = bbox_left
X = bbox_right
And substituting those values into the correct forms of the line equations, will give you the intersection point on that line.
eg IntersectX = bbox_left; IntersectY = m * bbox_left + c;

It is then a simple matter of checking which point is within the bounds of your wall object, and if there are multiple intersections, which one would occur first.

This is just a rough outline, which you’d be able to use. Would have less operations than checking pixel by pixel, so a faster option. (Especially if you’re using large wall objects).
 
T

Thadd

Guest
If your wall is an axis aligned square or rectangular object, you can easily determine the position of collision assuming you know two points of the projectiles path, and that path is a straight line.



Can still be done with an Orientated object too, but requires more math. If your path is not straight you have to adjust the line formula to the shape of the curve (parabolic most likely).

So you would save the original spawn point of the projectile OriginX, OriginY.

You can then work out the gradient of the path of the projectile, and the y intersect to give you the standard linear line equation in the form: y = mx + c, or rearranged form: x = (y - c) / m.
m = (projectile.OriginY - projectile.y)/ (projectile.OriginX - projectile.x);
c = OriginY - m * OriginX;

From here you can find at what point on any of the 4 sides it intersects, by using the follow line equations:
Y = bbox_top
Y = bbox_bottom
X = bbox_left
X = bbox_right
And substituting those values into the correct forms of the line equations, will give you the intersection point on that line.
eg IntersectX = bbox_left; IntersectY = m * bbox_left + c;

It is then a simple matter of checking which point is within the bounds of your wall object, and if there are multiple intersections, which one would occur first.

This is just a rough outline, which you’d be able to use. Would have less operations than checking pixel by pixel, so a faster option. (Especially if you’re using large wall objects).
Thanks, and I actually figured out how to do it. Using the physics variables already in GM, but they don't work. And give error, help please?
 

zbox

Member
GMC Elder
Necrobump but here is a GMS2 version of that very useful script seeing as it won't work directly now:
Code:
/// @param id1
/// @param id2

//determines the collision point of 2 instances
//return 0 if no collision point
//return 1 if collision point
//you should call it on collision
//it will work if the 2 instances overlap or if 2 egdes are touching (Better result)
//so make sure you move them into contact with move contact solid.
//sets the following instance variables for you to get the region 
//(Eventually set to about a 1x1 region)
//__left;
//__top;
//__right;
//__bottom;
//__x is the average (center of rect)
//__y is the average (center of rect)
if(argument_count > 2)
{
    var left,top,right,bottom;
    left = argument[3];
    top = argument[4];
    right = argument[5];
    bottom = argument[6];
    if(right-left < 1)
    if(bottom-top < 1)
        return 1;
    if(right-left > bottom-top)
    {
        if(collision_rectangle(left,top,(left+right)/2,bottom,argument[0],true,false))
        if(collision_rectangle(left,top,(left+right)/2,bottom,argument[1],true,false))
        {
            __left = left;
            __top = top;
            __right = (left+right)/2;
            __bottom = bottom;
            __x = (__left+__right)/2;
            __y = (__top+__bottom)/2;
            if(CollisionPointIDs(argument[0],argument[1],argument[2],__left,__top,__right,__bottom))
            {
                if(__right-__left < 1)
                if(__bottom-__top < 1)
                return 1;
            }
        }
        if(collision_rectangle((left+right)/2,top,right,bottom,argument[0],true,false))
        if(collision_rectangle((left+right)/2,top,right,bottom,argument[1],true,false))
        {
            __left = (left+right)/2;
            __top = top;
            __right = right;
            __bottom = bottom;
            __x = (__left+__right)/2;
            __y = (__top+__bottom)/2;
            if( CollisionPointIDs(argument[0],argument[1],argument[2],__left,__top,__right,__bottom))
            {
                if(__right-__left < 1)
                if(__bottom-__top < 1)
                return 1;
            }
        }
        return 0;
    }
    else
    {
        if(collision_rectangle(left,top,right,(top+bottom)/2,argument[0],true,false))
        if(collision_rectangle(left,top,right,(top+bottom)/2,argument[1],true,false))
        {
            __left = left;
            __top = top;
            __right = right;
            __bottom = (top+bottom)/2;
            __x = (__left+__right)/2;
            __y = (__top+__bottom)/2;
            if( CollisionPointIDs(argument[0],argument[1],argument[2],__left,__top,__right,__bottom))
            {
                if(__right-__left < 1)
                if(__bottom-__top < 1)
                return 1;
            }
        }
        if(collision_rectangle(left,(top+bottom)/2,right,bottom,argument[0],true,false))
        if(collision_rectangle(left,(top+bottom)/2,right,bottom,argument[1],true,false))
        {
            __left = left;
            __top = (top+bottom)/2;
            __right = right;
            __bottom = bottom;
            __x = (__left+__right)/2;
            __y = (__top+__bottom)/2;
            if( CollisionPointIDs(argument[0],argument[1],argument[2],__left,__top,__right,__bottom))
            {
                if(__right-__left < 1)
                if(__bottom-__top < 1)
                return 1;
            }
        }
        return 0;
    }
}
else
{
    var xx,yy,xxx,yyy;
   
    xx = max(argument[0].bbox_left-1,argument[1].bbox_left-1)
    yy = max(argument[0].bbox_top-1,argument[1].bbox_top-1)
    xxx = min(argument[0].bbox_right+1,argument[1].bbox_right+1)
    yyy = min(argument[0].bbox_bottom+1,argument[1].bbox_bottom+1)
   
    if(xx>xxx) return 0;
    if(yy>yyy) return 0;
   
    __left = xx;
    __top = yy;
    __right = xxx;
    __bottom = yyy;
    __x = (xx+xxx)/2
    __y = (yy+yyy)/2
    return CollisionPointIDs(argument[0],argument[1],1,__left,__top,__right,__bottom);
}
 
Top