Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get my collision to be more solid?

I'm working on a game in C# with XNA and I've been learning program in C# thanks to Nick Gravelyn's tutorials, but I've hit a snag. While I'm using Nick's collision system, I'm not using his player code. I'm using one that's based on a tutorial by Fatso784 that I've modified. So, as a result, I'm having trouble making my modified version of the collision system work properly. I've got it to the point that it pushes the player out of certain tiles, but I need it to be more solid because the player is still able walk through walls occasionally. I'm pretty sure I'm handling the collision wrong, but it could be that the collision is a little mushy. So here's the relevant code from my player class, the move code:

public void Move()
    {
        pos.X = bounds.X;
        pos.Y = bounds.Y;

        offsetPos.X = bounds.Width;
        offsetPos.Y = bounds.Height;

        if (frameCount % delay == 0)
        {
            switch (direction)
            {
                case "stand":
                    if (sideCollide == "none")
                    {
                        Equalize(2);
                    }
                    else if (sideCollide == "left")
                    {
                        speed += 1f;
                    }
                    else if (sideCollide == "right")
                    {
                        speed -= 1f;
                    }
                    bounds.X += (int)speed;
                    if (frameCount / delay >= 8)
                        frameCount = 0;
                    srcBounds = new Rectangle(frameCount / delay * 64, 0, 64, 64);
                    break;
                case "left":
                    if (sideCollide != "left")
                    {
                        if (speed > -maxspeed)
                        {
                            speed -= acceleration;
                        }
                        else if (speed < -maxspeed)
                        {
                            speed -= acceleration;
                            speed += drag;
                            Equalize(2);
                        }
                        speed += friction;
                    }
                    bounds.X += (int)speed;
                    if (frameCount / delay >= 4)
                        frameCount = 0;
                    srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
                    break;
                case "right":
                    if (sideCollide != "right")
                    {
                        if (speed < maxspeed)
                        {
                            speed += acceleration;
                        }
                        else if (speed > maxspeed)
                        {
                            speed += acceleration;
                            speed -= drag;
                            Equalize(2);
                        }
                        speed -= friction;
                    }
                    bounds.X += (int)speed;
                    if (frameCount / delay >= 4)
                        frameCount = 0;
                    srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
                    break;
                case "up":
                    if (speed > -4 && speed < 4)
                        srcBounds.Y = 128;
                    else
                        srcBounds.Y = 64;
                    if (srcBounds.Y == 0 || srcBounds.Y == 128)
                    {
                        if (jumpCount < 2)
                        {
                            if (frameCount / delay >= 9)
                                frameCount = 0;
                        }
                        else if (jumpCount > 2 && jumpCount <= 10)
                        {
                            if (frameCount / delay > 3)
                                frameCount = 2 * delay;
                        }
                        else if (jumpCount > 10 && jumpCount <= 18)
                        {
                            if (frameCount / delay > 5)
                                frameCount = 4 * delay;
                        }
                        else if (jumpCount > 18)
                        {
                            if (frameCount / delay >= 9)
                                frameCount = 0;
                        }

                        srcBounds = new Rectangle(frameCount / delay * 64, 128, 64, 64);
                    }
                    else if (srcBounds.Y == 64)
                    {
                        if (frameCount / delay >= 4)
                            frameCount = 0;
                        if (jumpCount <= 10)
                            srcBounds = new Rectangle((frameCount / delay) / 2 * 64, 64, 64, 64);
                        else
                            srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
                    }
                    if (jumpCount == 0)
                        startY = bounds.Y;
                    bounds = new Rectangle(bounds.X + (int)speed,
                        (jumpCount - 10) * (jumpCount - 10) - 100 + startY, 64, 64);
                    jumpCount++;
                    if (bounds.Y > startY)
                    {
                        bounds.Y = startY;
                        direction = "stand";
                        jumpCount = 0;
                    }
                    break;
            }
        }

        frameCount++;
    }

And the collision code:

public void CollideOutside(TileMap tilemap)
    {
        Point cell = Engine.PointCell(PlayerCenter);

        Point? upLeft = null, Up = null, upRight = null, Right = null, downRight = null, Down = null, downLeft = null, Left = null;

        if (cell.Y > 0)
        {
            Up = new Point(cell.X, cell.Y - 1);
        }
        if (cell.Y < tilemap.collisionMap.HeightinPixels)
        {
            Down = new Point(cell.X, cell.Y + 1);
        }
        if (cell.X > 0)
        {
            Left = new Point(cell.X - 1, cell.Y);
        }
        if (cell.X < tilemap.collisionMap.WidthinPixels)
        {
            Right = new Point(cell.X + 1, cell.Y);
        }

        if (cell.X > 0 && cell.Y > 0)
        {
            upLeft = new Point(cell.X - 1, cell.Y - 1);
        }
        if (cell.X < tilemap.collisionMap.WidthinPixels - 1 && cell.Y > 0)
        {
            upRight = new Point(cell.X + 1, cell.Y - 1);
        }
        if (cell.X > 0 && cell.Y < tilemap.collisionMap.HeightinPixels - 1)
        {
            downLeft = new Point(cell.X - 1, cell.Y + 1);
        }
        if (cell.X < tilemap.collisionMap.WidthinPixels - 1 && cell.Y < tilemap.collisionMap.Height - 1)
        {
            downRight = new Point(cell.X + 1, cell.Y + 1);
        }

        if (Up != null && tilemap.collisionMap.GetCellIndex(Up.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(Up.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {

            }
        }
        if (Down != null && tilemap.collisionMap.GetCellIndex(Down.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(Down.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {

            }
        }
        if (Right != null && tilemap.collisionMap.GetCellIndex(Right.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(Right.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {
                speed = -1f;
                sideCollide = "right";
            }
            else
            {
                sideCollide = "none";
            }
        }
        if (Left != null && tilemap.collisionMap.GetCellIndex(Left.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(Left.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {
                speed = 1f;
                sideCollide = "left";
            }
            else
            {
                sideCollide = "none";
            }
        }
        if (upLeft != null && tilemap.collisionMap.GetCellIndex(upLeft.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(upLeft.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {

            }
        }
        if (upRight != null && tilemap.collisionMap.GetCellIndex(upRight.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(upRight.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {

            }
        }
        if (downLeft != null && Left != null && tilemap.collisionMap.GetCellIndex(downLeft.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(downLeft.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {
                speed = 1f;
                sideCollide = "left";
            }
        }
        if (downRight != null && Right != null && tilemap.collisionMap.GetCellIndex(downRight.Value) == 1)
        {
            Rectangle rect = Engine.CreateCell(downRight.Value);
            Rectangle playerCell = Boundary;

            if (rect.Intersects(playerCell))
            {
                speed = -1f;
                sideCollide = "right";
            }
        }

        if (Right == null && Left == null)
        {
            sideCollide = "none";
        }
    }

    public Rectangle Boundary
    {
        get
        {
            Rectangle rect = bounds;
            rect.X = (int)pos.X;
            rect.Y = (int)pos.Y;
            return rect;
        }
    }

So how can I improve the collision?

like image 278
IronGiraffe Avatar asked May 13 '11 05:05

IronGiraffe


1 Answers

This answer is mostly in response to Tim's answer - because he's given very much the wrong approach. (Your question is a very large code dump, I can't really play spot-the-error with that much code.)

The trick with collision detection - the way the "real" physics engines do it - is to always treat your objects as solids. You always - each frame - check objects for interpenetration, and then separate them if they interpenetrate.

If you only test for moving across the boundary of an object you are going to miss collisions. This includes any approach where you attempt to predict and avoid a collision by snapping onto the surface. If you do this, you are just asking for floating-point precision errors to let you slip inside objects.

EDIT: of course, your code all seems to be integer-based (Point and Rectangle). So at least floating-point precision should not be an issue. But maybe you have a < where you should have a <= or something to that effect?

(Also you're using strings in a lot of places where you very much should be using enumerations.)

like image 83
Andrew Russell Avatar answered Nov 01 '22 10:11

Andrew Russell