0
\$\begingroup\$

I'm making a simple game in C# and i'm having some problems with rectangle collisions. What i'm doing is checking if two rectangles intersects, checking the side of collision and then moving the rectangles away from each other.

What is working:

  • The rectangle intersection check is working.
  • The left and right rectangle side collisions is working.

What is not working:

  • When debugging, the collision check only prints "Touching Left/Right".
  • When i move a rectangle to the top or bottom side of another, the first rectangle jumps to the other side.
  • The second problem works correctly if the first rectangle is alligned perfectly to the top or bottom side.

Here is a snippet of the code that i currently have, i hope someone can help me solve these problems.

public RectangleF Bounds { get { return new RectangleF(position.x, position.y, scale.width, scale.height); } }

public void CheckCollisions(Transform first, Transform second)
{
    if(first.Bounds.IntersectsWith(second.Bounds) == false) return;

    bool touching_left =
    first.Bounds.Right > second.Bounds.Left &&
    first.Bounds.Left < second.Bounds.Left &&
    first.Bounds.Bottom > second.Bounds.Top &&
    first.Bounds.Top < second.Bounds.Bottom;

    bool touching_right =
    first.Bounds.Left < second.Bounds.Right &&
    first.Bounds.Right > second.Bounds.Right &&
    first.Bounds.Bottom > second.Bounds.Top &&
    first.Bounds.Top < second.Bounds.Bottom;

    bool touching_top =
    first.Bounds.Bottom > second.Bounds.Top &&
    first.Bounds.Top < second.Bounds.Top &&
    first.Bounds.Right > second.Bounds.Left &&
    first.Bounds.Left < second.Bounds.Right;

    bool touching_bottom =
    first.Bounds.Top < second.Bounds.Bottom &&
    first.Bounds.Bottom > second.Bounds.Bottom &&
    first.Bounds.Right > second.Bounds.Left &&
    first.Bounds.Left < second.Bounds.Right;

    if (touching_left)
    {
        float x = second.Bounds.Left - first.Bounds.Width;
        float y = first.Bounds.Y;

        first.SetPosition(new PointF(x, y));
        Debug.WriteLine("Touching LEFT");
    }

    else if (touching_right)
    {
        float x = second.Bounds.Right;
        float y = first.Bounds.Y;

        first.SetPosition(new PointF(x, y));
        Debug.WriteLine("Touching RIGHT");
    }

    else if (touching_top)
    {
        float x = first.Bounds.X;
        float y = second.Bounds.Top - first.Bounds.Height;

        first.SetPosition(new PointF(x, y));
        Debug.WriteLine("Touching TOP");
    }

    else if (touching_bottom)
    {
        float x = first.Bounds.X;
        float y = second.Bounds.Bottom;
        
        first.SetPosition(new PointF(x, y));
        Debug.WriteLine("Touching BOTTOM");
    }
}

Thanks in advance!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ If you disable the code that moves the rectangle after collision - do the debug lines show correct sides? \$\endgroup\$ Aug 31, 2021 at 13:04
  • \$\begingroup\$ Hello, i just tested it and it prints only "Touching LEFT" and "Touching RIGHT". What could be causing it? \$\endgroup\$
    – RickS.
    Aug 31, 2021 at 13:27

2 Answers 2

3
\$\begingroup\$

We can make this a bit simpler using Minkowski addition. We can shrink one rectangle down to its center point, and enlarge the other rectangle from its center by the first's width and height, and get an equivalent situation: the two original rectangles overlap if and only if the shrunken point overlaps the new enlarged rectangle.

Then we can just pick a new place for that center point, and transform that back into a non-overlapping position for the original rectangle. This lets us resolve the collision in one pass, touching only x or y, and only once - rather than stacking a vertical collision resolution after a horizontal resolution using outdated info.

We call this the "minimum translation vector" - using the smallest adjustment possible to remove the overlap, whether that's horizontal or vertical.

if(first.Bounds.IntersectsWith(second.Bounds) == false) return;

// Compute the center of each rectangle:
var firstCenter = new PointF( first.Left  + first.Width  /2f,
                              first.Top   + first.Height /2f );

var secondCenter = new PointF(second.Left + second.Width /2f,
                              second.Top  + second.Height/2f);

// Compute the offset from the second's center to the first.
var offset = firstCenter - secondCenter;

// Compute how deeply we're overlapping, horizontally and vertically.
var penetration = new PointF((first.Width  + second.Width )/2f,
                             (first.Height - second.Height)/2f);
                - new PointF(Math.Abs(offset.x), Math.Abs(offset.y));

if (   penetration.y <= 0 
   || (penetration.x  > 0 && penetration.x < penetration.y)) {
    // If we're only overlapping on the x,
    // or we're overlapping on the x less than on the y,
    // then correct our x position for the minimum safe correction.
    offset.x = Math.Sign(offset.x) * (first.Width  + second.Width )/2f;
} else {
    // Otherwise, our minimum safe correction is vertical.
    offset.y = Math.Sign(offset.y) * (first.Height + second.Height)/2f;
}

// Now form the top-left corner of the first rectangle at
// this new position.
var corner = new PointF(secondCenter.x + offset.x - first.Width /2f,
                        secondCenter.y + offset.y - first.Height/2f);

first.SetPosition(corner);
\$\endgroup\$
6
  • \$\begingroup\$ Hello, the code is working but sometimes the first rectangle moves through the second and also sometimes it behaves strangely when touching the bottom or top of the second rectangle. Thanks for the answer anyways. Edit: I tested again and the top/bottom collision is not working correctly using your code. \$\endgroup\$
    – RickS.
    Aug 31, 2021 at 14:58
  • 2
    \$\begingroup\$ I'd need more specific repro cases than "sometimes" and more specific symptoms than "strangely" and "not correct" to be able to help you. \$\endgroup\$
    – DMGregory
    Aug 31, 2021 at 15:03
  • \$\begingroup\$ Nevermind, i got it to work. Thank you for the answer. \$\endgroup\$
    – RickS.
    Aug 31, 2021 at 19:24
  • \$\begingroup\$ Any edits to propose to the answer, or were the problems unrelated to the text above? \$\endgroup\$
    – DMGregory
    Aug 31, 2021 at 19:29
  • 3
    \$\begingroup\$ I'll note this is the second time you've commented on an answer saying there's a problem that eventually turned out to be in your implementation, not a problem in the answer. This might be a sign that you need to review and test your code more carefully. \$\endgroup\$
    – DMGregory
    Aug 31, 2021 at 20:19
3
\$\begingroup\$

Preface

The answer from @DMGregory has some wonderful math that will most likely solve most if not all of your problems. While I would recommend working towards an implementation like theirs, I thought it would also be helpful to answer some of your other questions about why your code behaves the way it does.

Q&A

Q: When debugging, the collision check only prints "Touching Left/Right".

A: Your debug messages are wrapped inside of: if ... else if ... blocks. After the first true evaluation the remaining else statements are skipped over. The else is preventing more than one message from being written. Since you have the LEFT and RIGHT blocks before the TOP and BOTTOM blocks it will stop as soon as either LEFT or RIGHT evaluate to true and wont continue to the TOP or BOTTOM sections. Try removing the else so that it reads if instead of else if. When you have 4 independant if statments you should see both a LEFT/RIGHT message and a TOP/BOTTOM message.

Q: When i move a rectangle to the top or bottom side of another, the first rectangle jumps to the other side.

A: Your code now uses boolean logic. It is either true or false. This means that it is all or nothing, and there is no in between. If the first rectangle touches the second at all even a little bit then the bool is true and you move it all the way to the other side. The math from the other answer will find that "in between" amount and only move it as much as is necessary.

Q: The second problem works correctly if the first rectangle is aligned perfectly to the top or bottom side.

A: Your code checks to see if the sides are > or if they are < but it doesn't check to see if they are perfectly =. If your two rectangles are the same size then when the two sides are = is the only case where the LEFT/RIGHT if ... else if ... blocks evaluate to false and allow the code to continue into the TOP/BOTTOM sections.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for the answer, it makes more sense now. \$\endgroup\$
    – RickS.
    Aug 31, 2021 at 15:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .