2D AABB Collision Resolution Multiple Corner Case Issues C#

Viewed 262

I've been at this for a few days, and I can't seem to get it perfect. I'm making a top down procedurally generated adventure game, and my collision resolution system is leaving something to be desired.

The Problem:

Attempting to move between 2 hitboxes where the gap normally would not let you enter, you can enter if you are moving diagonally, this can either cause an infinite loop where the collision is never removed from the list of collisionsToResolve (rare), or temporarily allows you to walk around freely inside a solid hitbox (most common effect).

The strange part is that other corner cases resolve correctly, i.e. if there is no gap, you don't clip, and you can slide along any solid hitbox in any direction without "catching" or anything if there are 2 next to each other. IN FACT this ONLY seems to occur when the gap you're trying to go through is exactly one pixel smaller than your hitbox, so what I'm guessing is happening is that when the collision is checked at the beginning of the frame, only one of the 2 hitboxes is being added, and when it resolves, it pushes you into the other one. I'm at a loss for how to combat this, so any help is much appreciated.

Video demonstrating the issues:

https://youtu.be/jARoEfy9u2Q

Code for collision resolution:

public void Collide(float newX, float newY, List<GameObject> colObj, int seed, GraphicsDeviceManager graphics, Player p) {

            //newx is intermediatePosition + float value of controller left stick X
            //newy "                                                            " Y
            //colObj is a list of gameobjects that are collidable that are within a certain distance to the player (possible collisions)

            var fr = new Rectangle((int)Math.Round(newX) + p.bbofsx, (int)Math.Round(newY) + p.bbofsy, p.boundingBox.Width, p.boundingBox.Height);

            for (int i = 0; i < colObj.Count; i++) {
                if (fr.Intersects(colObj[i].boundingBox)) {
                    colObj[i].distanceToPlayer = (int)AABB.GetDistanceSquared(fr, colObj[i].boundingBox);
                    p.collisionsToResolve.Add(colObj[i]);
                }
            }

            //order list
            p.collisionsToResolve = p.collisionsToResolve.OrderBy(o => o.distanceToPlayer).ToList();

            for (int w = 0; w < p.collisionsToResolve.Count; w++) {

                //overworld collision code
                    var side = AABB.GetCollisionSide(p.boundingBox, p.collisionsToResolve[w].boundingBox, new Vector2((float)Math.Round(newX) - p.worldPosition.X, (float)Math.Round(newY) - p.worldPosition.Y));

                    switch (side) {
                        case CollisionSide.Left:
                            while (fr.Intersects(p.collisionsToResolve[w].boundingBox)) {
                                newX--;
                                p.intermediateWorldPosition.X = p.worldPosition.X;
                                fr = new Rectangle(p.boundingBox.X + (int)((float)Math.Round(newX) - p.worldPosition.X), p.boundingBox.Y + (int)((float)Math.Round(newY) - p.worldPosition.Y), p.boundingBox.Width, p.boundingBox.Height);
                            }
                            break;
                        case CollisionSide.Right:
                            while (fr.Intersects(p.collisionsToResolve[w].boundingBox)) {
                                newX++;
                                p.intermediateWorldPosition.X = p.worldPosition.X;
                                fr = new Rectangle(p.boundingBox.X + (int)((float)Math.Round(newX) - p.worldPosition.X), p.boundingBox.Y + (int)((float)Math.Round(newY) - p.worldPosition.Y), p.boundingBox.Width, p.boundingBox.Height);
                            }
                            break;
                        case CollisionSide.Top:
                            while (fr.Intersects(p.collisionsToResolve[w].boundingBox)) {
                                newY--;
                                p.intermediateWorldPosition.Y = p.worldPosition.Y;
                                fr = new Rectangle(p.boundingBox.X + (int)((float)Math.Round(newX) - p.worldPosition.X), p.boundingBox.Y + (int)((float)Math.Round(newY) - p.worldPosition.Y), p.boundingBox.Width, p.boundingBox.Height);
                            }
                            break;
                        case CollisionSide.Bottom:
                            while (fr.Intersects(p.collisionsToResolve[w].boundingBox)) {
                                newY++;
                                p.intermediateWorldPosition.Y = p.worldPosition.Y;
                                fr = new Rectangle(p.boundingBox.X + (int)((float)Math.Round(newX) - p.worldPosition.X), p.boundingBox.Y + (int)((float)Math.Round(newY) - p.worldPosition.Y), p.boundingBox.Width, p.boundingBox.Height);
                            }
                            break;
                    }

                p.UpdateBoundingBox();

                for (int i = 0; i < colObj.Count; i++) {
                    if (fr.Intersects(colObj[i].boundingBox)) {
                        p.collisionsToResolve.RemoveAt(w);
                        colObj[i].distanceToPlayer = (int)AABB.GetDistanceSquared(p.boundingBox, colObj[i].boundingBox);
                        p.collisionsToResolve.Add(colObj[i]);
                        p.collisionsToResolve = p.collisionsToResolve.OrderBy(o => o.distanceToPlayer).ToList();
                        w = 0;
                    }
                }

                var done = true;
                for (int i = 0; i < p.collisionsToResolve.Count; i++) {
                    if (fr.Intersects(p.collisionsToResolve[i].boundingBox)) {
                        done = false;
                        break;
                    }
                }

                if (done) {
                    break;
                }
            }
            //move player and update bounding box

            p.worldPosition.X = (float)Math.Round(newX);
            p.worldPosition.Y = (float)Math.Round(newY);

            p.UpdateBoundingBox();
        }
1 Answers

In case this helps anyone else, I figured out the way to solve it, instead of trying to resolve the collision at the new position (x and y), first resolve at the new x position, then the y position (or vice versa). You can choose which axis to resolve first dynamically based on the positions of the 2 bounding boxes.

Related