Zach
Zach

Reputation: 131

BoundingBoxes for Collision Detection overlapping and causing issues

The following code is my attempt at stopping the PC (player character) from walking through an NPC.

Code:

if (PC.charSprite.BoundingBox.Intersects(npc.charSprite.BoundingBox))       
{        
    Rectangle collisionBox = Rectangle.Intersect(PC.charSprite.BoundingBox, npc.charSprite.BoundingBox);

    if (PC.moveDir.X > 0) //Moving Right
    {
        //Unknown Code Goes Here
    }
    else if (PC.moveDir.X < 0) //Moving Left
    {

    }
    else if (PC.moveDir.Y > 0) //Moving Down
    {

    }
    else if (PC.moveDir.Y < 0) //Moving Up
    {

    }
}

How do I make it so that when the PC touches an NPC, the PC stops moving in that direction but is free to move any of the other three directions?

Code I have tried:

if (PC.moveDir.X > 0) //Moving Right
{
    PC.moveDir = Vector2.Zero;
}
else if (PC.moveDir.X < 0) //Moving Left
{
    PC.moveDir = Vector2.Zero;
}
else if (PC.moveDir.Y > 0) //Moving Down
{
    PC.moveDir = Vector2.Zero;
}
else if (PC.moveDir.Y < 0) //Moving Up
{
    PC.moveDir = Vector2.Zero;
}

^This locks the PC in place, preventing any and all movement.

if (PC.moveDir.X > 0) //Moving Right
{
    PC.moveDir.X = 0;
}
else if (PC.moveDir.X < 0) //Moving Left
{
    PC.moveDir.X = 0;
}
else if (PC.moveDir.Y > 0) //Moving Down
{
    PC.moveDir.Y = 0;
}
else if (PC.moveDir.Y < 0) //Moving Up
{
    PC.moveDir.Y = 0;
}

^This, also, locks the PC in place.

if (PC.moveDir.X > 0) //Moving Right
{
    PC.moveDir.X = -currentspeed;
}
else if (PC.moveDir.X < 0) //Moving Left
{
    PC.moveDir.X = currentspeed;
}
else if (PC.moveDir.Y > 0) //Moving Down
{
    PC.moveDir.Y = -currentspeed;
}
else if (PC.moveDir.Y < 0) //Moving Up
{
    PC.moveDir.Y = currentspeed;
}

^This works on a terribly intermittent basis. I had hoped that by changing moveDir into the opposite direction of how much it had overlapped by (or greater) would keep the PC at the edge of the NPC but prevent overlap. Unfortunately, half the time the PC gets stuck in place.

Combining the two

if (PC.moveDir.X > 0) //Moving Right
{
    PC.moveDir.X = -currentspeed;
    PC.moveDir.X = 0;
}
else if (PC.moveDir.X < 0) //Moving Left
{
    PC.moveDir.X = currentspeed;
    PC.moveDir.X = 0;
}
else if (PC.moveDir.Y > 0) //Moving Down
{
    PC.moveDir.Y = -currentspeed;
    PC.moveDir.Y = 0;
}
else if (PC.moveDir.Y < 0) //Moving Up
{
    PC.moveDir.Y = currentspeed;
    PC.moveDir.Y = 0;
}

Just causes overall lockup.

if (PC.moveDir.X > 0) //Moving Right
{
    PC.moveDir.X = -collisionBox.Width;
}
else if (PC.moveDir.X < 0) //Moving Left
{
    PC.moveDir.X = collisionBox.Width;
}
else if (PC.moveDir.Y > 0) //Moving Down
{
    PC.moveDir.Y = -collisionBox.Height;
}
else if (PC.moveDir.Y < 0) //Moving Up
{
    PC.moveDir.Y = collisionBox.Height;
}

^This almost works perfectly, but when the PC is up against the NPC and turns perpendicular to the NPC it is touching, the PC jumps to one side. Again, about half the time.

Attempt inspired from comment by CSJ:

if (PC.charSprite.BoundingBox.Intersects(npc.charSprite.BoundingBox))
{
    Rectangle collisionBox = Rectangle.Intersect(PC.charSprite.BoundingBox, npc.charSprite.BoundingBox);
    if (PC.moveDir.X > 0) //Moving Right
    {
        PC.charSprite.Position = new Vector2(npc.charSprite.BoundingBox.Left - 34, PC.charSprite.Position.Y);
    }
    else if (PC.moveDir.X < 0) //Moving Left
    {
        PC.charSprite.Position = new Vector2(npc.charSprite.BoundingBox.Right + 2, PC.charSprite.Position.Y);
    }
    else if (PC.moveDir.Y > 0) //Moving Down
    {
        PC.charSprite.Position = new Vector2(PC.charSprite.Position.X, npc.charSprite.BoundingBox.Top - 34);
    }
    else if (PC.moveDir.Y < 0) //Moving Up
    {
        PC.charSprite.Position = new Vector2(PC.charSprite.Position.X, npc.charSprite.BoundingBox.Bottom + 2)
    }
}

I ask again: How do I make it so that when the PC touches an NPC, the PC stops moving in that direction but is free to move any of the other three directions?

Or, in more generic terms, how do I make one Rectangle intersecting with another Rectangle lose its ability to move towards the Rectangle it is intersecting without impeding its movement in any other direction?

Upvotes: 2

Views: 916

Answers (2)

Zach
Zach

Reputation: 131

After [i]much[/i] headbanging and alcohol drinking, I finally came up with a solution. I even placed it into a class so that others may use it to help them with similar problems.

class Collision
{
    #region Declarations
    private Rectangle rectangle1;
    private Rectangle rectangle2;
    private Rectangle collisionZone;
    #endregion

    #region Constructors
    public Collision(Rectangle R1, Rectangle R2)
    {
        rectangle1 = R1;
        rectangle2 = R2;
        if(AreColliding())
        {
            collisionZone = Rectangle.Intersect(rectangle1, rectangle2);
        }
        else
        {
            collisionZone = Rectangle.Empty;
        }
    }
    #endregion

    #region Properties
    /// <summary>
    /// Returns the x-axis value of the top-left corner of R1
    /// </summary>
    public int TopLeftR1X
    {
        get { return rectangle1.X; }
    }

    /// <summary>
    /// Returns the y-axis value of the top-left corner of R1
    /// </summary>
    public int TopLeftR1Y
    {
        get { return rectangle1.Y; }
    }

    /// <summary>
    /// Returns the x-axis value of the top-right corner of R1
    /// </summary>
    public int TopRightR1X
    {
        get { return rectangle1.X + rectangle1.Width; }
    }

    /// <summary>
    /// Returns the y-axis value of the top-right corner of R1
    /// </summary>
    public int TopRightR1Y
    {
        get { return rectangle1.Y; }
    }

    /// <summary>
    /// Returns the x-axis value of the bottom-left corner of R1
    /// </summary>
    public int BottomLeftR1X
    {
        get { return rectangle1.X; }
    }

    /// <summary>
    /// Returns the y-axis value of the bottom-left corner of R1
    /// </summary>
    public int BottomLeftR1Y
    {
        get { return rectangle1.Y + rectangle1.Height; }
    }

    /// <summary>
    /// Returns the x-axis value of the bottom-right corner of R1
    /// </summary>
    public int BottomRightR1X
    {
        get { return rectangle1.X + rectangle1.Width; }
    }

    /// <summary>
    /// Returns the y-axis value of the bottom-right corner of R1
    /// </summary>
    public int BottomRightR1Y
    {
        get { return rectangle1.Y + rectangle1.Height; }
    }

    /// <summary>
    /// Returns the x-axis value of the top-left corner of R2
    /// </summary>
    public int TopLeftR2X
    {
        get { return rectangle2.X; }
    }

    /// <summary>
    /// Returns the y-axis value of the top-left corner of R2
    /// </summary>
    public int TopLeftR2Y
    {
        get { return rectangle2.Y; }
    }

    /// <summary>
    /// Returns the x-axis value of the top-right corner of R2
    /// </summary>
    public int TopRightR2X
    {
        get { return rectangle2.X + rectangle2.Width; }
    }

    /// <summary>
    /// Returns the y-axis value of the top-right corner of R2
    /// </summary>
    public int TopRightR2Y
    {
        get { return rectangle2.Y; }
    }

    /// <summary>
    /// Returns the x-axis value of the bottom-left corner of R2
    /// </summary>
    public int BottomLeftR2X
    {
        get { return rectangle2.X; }
    }

    /// <summary>
    /// Returns the y-axis value of the bottom-left corner of R2
    /// </summary>
    public int BottomLeftR2Y
    {
        get { return rectangle2.Y + rectangle2.Height; }
    }

    /// <summary>
    /// Returns the x-axis value of the bottom-right corner of R2
    /// </summary>
    public int BottomRightR2X
    {
        get { return rectangle2.X + rectangle2.Width; }
    }

    /// <summary>
    /// Returns the y-axis value of the bottom-right corner of R2
    /// </summary>
    public int BottomRightR2Y
    {
        get { return rectangle2.Y + rectangle2.Height; }
    }

    /// <summary>
    /// Returns the rectangle formed by how much the rectangles overlap.
    /// </summary>
    public Rectangle Overlap
    {
        get { return collisionZone; }
    }

    #endregion

    #region Methods

    public bool AreColliding()
    {
        if (rectangle1.Intersects(rectangle2))
        {
            return true;
        }
        else
        {
            return false;
        }

    }

    public Vector2 StopOnCollision(Vector2 position, Vector2 moveDir, int currentspeed)
    {
        if (Overlap.Width < Overlap.Height)
        {
            if (position.X < rectangle2.Left)
            {
                if (moveDir.X > 0) //Moving Right
                {
                    moveDir = Vector2.Zero;
                }
                else
                {
                    moveDir.X = -currentspeed;
                    moveDir.Y = 0;
                }
            }
            //else if ((position.X + 33) > rectangle2.Right)
            else if (position.X < rectangle2.Right)
            {
                if (moveDir.X < 0) //Moving Left
                {
                    moveDir = Vector2.Zero;
                }
                else
                {
                    moveDir.X = currentspeed;
                    moveDir.Y = 0;
                }
            }
        }
        else
        {
            if (Overlap.Y == rectangle2.Top)

            {
                if (moveDir.Y > 0) //Moving Down
                {
                    moveDir = Vector2.Zero;
                }
                else
                {
                    moveDir.Y = -currentspeed;
                    moveDir.X = 0;
                }
            }

            else
            {
                if (moveDir.Y < 0) //Moving Up
                {
                    moveDir = Vector2.Zero;
                }
                else
                {
                    moveDir.Y = currentspeed;
                    moveDir.X = 0;
                }
            }
        }

        return moveDir;
    }

    #endregion
}

It's somewhat simple: - You instantiate the class with the two collision boxes (rectangles) you are checking. - You can check to make sure that they, in fact, are colliding. - To use StopOnCollision, you input the position of whatever is moving (the x,y coordinate of reference), the vector that you wish to use to alter or impede its movement, and the speed at which the object is moving (pixels per frame)

I hope this helps everyone else as much as it helped me

Upvotes: 0

CSJ
CSJ

Reputation: 3941

What I've done in this kind of situation is:

  • Carry out the displacement normally (i.e. add the character's motion vector to his position)
  • Check all obstacles for collision. If there is a collision, "teleport" the character away from the obstacle, say, one pixel. Continue doing this until there is no collision anymore.

It's probably not the fastest running solution, but it gets the job done. Works well when there are many dynamic things that can collide, too.

Upvotes: 1

Related Questions