4
\$\begingroup\$

Okay, this is really starting to annoy me.

First of all, detecting collisions is nice and easy. I have a list of tiles, and a player. The player and each tile have their own rectangles. (The tiles are 16x16, the player is 10x14). To detect a collision, I simply iterate through the list of tiles to see if the player is colliding with any of them, and if so, set a value to true.

^The above works all fine and dandy, but I've tried just about every way I can think of to respond to that collision. I've ended up with some almost there but buggy results, and I can't find a single tutorial on 2D collision RESPONSE. Sure, there are thousands of tutorials out there about detection, but I'm having more trouble then I would think I would when I look for stuff about response.

The two methods I use for collision are;

        public bool CanMove(Rectangle playerRect, List<Tile> tiles, Direction d, Vector2 speed)
    {
        bool b = true;
        Rectangle r = new Rectangle();


        if (d == Direction.Up)
        {
            foreach (Tile ti in tiles)
            {
                r = new Rectangle(playerRect.X, (int)(playerRect.Y - speed.Y), playerRect.Width, playerRect.Height);
                if (r.Intersects(ti.rect))
                {
                    b = false;
                    return b;
                }
            }
        }
        if (d == Direction.Down)
        {
            int adjust = 1;

            if (playerPos.Y < 0)
                adjust -= 1;

            foreach (Tile ti in tiles)
            {
                r = new Rectangle(playerRect.X, (int)(playerRect.Y + speed.Y + adjust), playerRect.Width, playerRect.Height);
                if (r.Intersects(ti.rect))
                {
                    if (Math.Abs(r.Y - ti.rect.Y) > 12.5f && Math.Abs(r.Y - ti.rect.Y) < 13.5f && Math.Abs(r.X - ti.rect.X) > 14.5f && Math.Abs(r.X - ti.rect.X) < 15.5f)
                    {
                        playerPos.Y -= 1;
                    }
                    b = false;
                    return b;
                }

            }
        }

        if (d == Direction.Gravity)
        {
            int adjust = 1;

            if (playerPos.Y < 0)
                adjust -= 1;

            foreach (Tile ti in tiles)
            {
                r = new Rectangle(playerRect.X, (int)(playerRect.Y + speed.Y + adjust), playerRect.Width, playerRect.Height);
                if (r.Intersects(ti.rect))
                {
                    if (Math.Abs(r.Y - ti.rect.Y) > 12.5f && Math.Abs(r.Y - ti.rect.Y) < 13.5f && Math.Abs(r.X - ti.rect.X) > 14.5f && Math.Abs(r.X - ti.rect.X) < 15.5f)
                    {
                        playerPos.Y -= 1;
                    }
                    //adjustTile = ti;
                    b = false;
                    return b;
                }

            }
        }

        if (d == Direction.Left)
        {
            foreach (Tile ti in tiles)
            {
                r = new Rectangle((int)(playerRect.X - speed.X), playerRect.Y, playerRect.Width, playerRect.Height);
                if (r.Intersects(ti.rect))
                {
                    adjustTile = ti;
                    b = false;
                    return b;
                }
            }
        }
        if (d == Direction.Right)
        {
            foreach (Tile ti in tiles)
            {
                r = new Rectangle((int)(playerRect.X + speed.X), playerRect.Y, playerRect.Width, playerRect.Height);
                if (r.Intersects(ti.rect))
                {
                    b = false;
                    return b;
                }
            }
        }



        return b;
    }

And the portion of my update method that's necessary;

                if (kbs.IsKeyDown(Keys.Right) && kbs.IsKeyUp(Keys.Left))
            {
                direction = Direction.Right;
            }
            if (kbs.IsKeyDown(Keys.Left) && kbs.IsKeyUp(Keys.Right))
            {
                direction = Direction.Left;
            }

            //if (IsColliding(playerRectangle, tileList))


            if (playerSpeed.Y < 0)
            {
                playerSpeed.Y += 0.15f;
            }
            else
            {
                playerSpeed.Y += 0.2f;
            }
            //playerPos.X += playerSpeed.X;

            if (kbs.IsKeyDown(Keys.Space) && pkbs.IsKeyUp(Keys.Space))
            {
                playerPos = Vector2.Zero;
                playerSpeed.Y = 0;
            }

            playerRectangle = new Rectangle((int)playerPos.X + 3, (int)playerPos.Y + 2, 10, 14);


            if (playerRectangle.Contains(ms.X - viewport.Width / 2, ms.Y - viewport.Height / 2))
            {
                player.rectY = 16;
            }
            else
            {
                player.rectY = 0;
            }

            if (CanMove(playerRectangle, tileList, Direction.Gravity, new Vector2(0, playerSpeed.Y)))
            {
                if (playerSpeed.Y == 0)
                   playerSpeed.Y = 1f;
                playerPos.Y += playerSpeed.Y;
            }
            else
            {
                canJump = true;
                    playerSpeed.Y = 0f;
                    //if (adjustTile != null && playerPos.Y != adjustTile.rect.Y - 15)
                    //{
                    //    playerPos.Y = adjustTile.rect.Y - 15;
                    //    adjustTile = null;
                    //}
                    playerPos.Y = playerRectangle.Y - 2;

            }
            if (CanMove(playerRectangle, tileList, Direction.Left, new Vector2(playerSpeed.X, 0)))
            {
                //playerPos.X -= playerSpeed.X;
                player.rectY = 0;

            }
            else
            {
                player.rectY = 16;
            }
            if (CanMove(playerRectangle, tileList, Direction.Right, new Vector2(playerSpeed.X, 0)))
            {
                //playerPos.X -= playerSpeed.X;
                player.rectY = 0;

            }
            else
            {
                player.rectY = 16;
            }


            #region Check input

            if (kbs.IsKeyDown(Keys.Left) && pkbs.IsKeyUp(Keys.Right))
            {
                player.flipped = true;
            }
            if (kbs.IsKeyDown(Keys.Right) && pkbs.IsKeyUp(Keys.Left))
            {
                player.flipped = false;
            }

            if (kbs.IsKeyDown(Keys.Up) && pkbs.IsKeyUp(Keys.Up))
            {
                if (CanMove(playerRectangle, tileList, Direction.Up, new Vector2(0, 2)))
                {
                    if (canJump)
                    {
                        playerSpeed.Y -= 4.2f;
                    }

                    player.rectY = 0;

                }
                else
                {
                    player.rectY = 16;
                }
            }
            if (kbs.IsKeyDown(Keys.Down))
            {
                if (CanMove(playerRectangle, tileList, Direction.Down, new Vector2(0, 2)))
                {
                    //playerRectangle.Y += 2;
                    player.rectY = 0;

                }
                else
                {
                    player.rectY = 16;
                }
            }
            if (kbs.IsKeyDown(Keys.Left))
            {

                playerSpeed.X = 2f;
                if (CanMove(playerRectangle, tileList, Direction.Left, new Vector2(playerSpeed.X, 0)))
                {
                    playerPos.X -= playerSpeed.X;
                    player.rectY = 0;

                }
                else
                {
                    player.rectY = 16;
                }

                if (player.rectY == 16)
                {
                    playerPos.Y -= 1;
                }

            }
            if (kbs.IsKeyDown(Keys.Right))
            {
                playerSpeed.X = 2f;
                if (CanMove(playerRectangle, tileList, Direction.Right, new Vector2(playerSpeed.X, 0)))
                {
                    playerPos.X += playerSpeed.X;
                    player.rectY = 0;

                }
                else
                { 
                    player.rectY = 16;
                }
                if (player.rectY == 16)
                {
                    if (playerPos.Y / 16 != (int)playerPos.Y / 16)
                    {
                        playerPos.Y -= 1;
                    }
                }
            }

            if (kbs.IsKeyUp(Keys.Right) && kbs.IsKeyUp(Keys.Left))
            {
                playerSpeed.X = 0;
            }

            #endregion

This is almost there, but there are one or two kinks in it.

If I'm up against a wall of tiles, and jump, I'll end up in the roof(If not going completely through it) if it's low enough.

The reason I add the 'playerpos.y -= 1' in certain spots is because the player would get stuck when coming down on a corner at the right angle.

Also, when the player goes to jump, and come back down, he will hit an invisible wall (usually about 8 pixels or so above the tile he would be landing on) before continuing to fall and land on the said tile.

Any tips or advice?

And yes, this is 2D, there is gravity and such, I'm trying to get it to work kinda like the way mario collisions work.

EDIT: The new code is actually what I came up with to start with, the HandleCollisions() thing was an attempt to try and implement the Platformer Starter kit code, which didn't work very well. The GetRectangleIntersectionDepth() might come in handy though.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ If efficiency is what you're worried about, you should consider not checking your player against every tile on the screen and instead check only the tiles nearby the player. \$\endgroup\$ Commented Dec 31, 2012 at 14:08
  • \$\begingroup\$ Yea, that should be fairly easy, but how many tiles your checking shouldn't have an effect on what your colliding with. Actually, I've rewritten my code (It seems a little sloppier to me, but it works better). I'm going to update the OP. \$\endgroup\$ Commented Dec 31, 2012 at 20:52

2 Answers 2

3
\$\begingroup\$

For very simple response:

  1. Store the position of the player in a temp variable
  2. Move the player
  3. Check for collision
  4. If there is collision, reset the player position to the temp position.

Depending on the speed of the player you might have a gap between the tile and the player.

\$\endgroup\$
6
  • \$\begingroup\$ It would be nice if that worked the way I want it to, but that approach is as buggy as a Volkswagen. Do you know of any...more advanced responses? \$\endgroup\$ Commented Dec 31, 2012 at 7:32
  • \$\begingroup\$ You say here and in your question that its buggy, but in what way? What is exactly going wrong? \$\endgroup\$
    – Roy T.
    Commented Dec 31, 2012 at 9:26
  • \$\begingroup\$ With gravity, when you end up "on the ground" it's incredibly jittery. It can at times not work at all depending on the angle. Sometimes it will shove the player into the block, which cause a chain reaction and you end up on the other side of the map. I'm looking for help with platformer style collision, not dungeon crawler style collision. \$\endgroup\$ Commented Dec 31, 2012 at 20:33
  • \$\begingroup\$ +1 this is pretty much the standard way to handle it. Jitter may be because you're using floats (draw ints instead), or you may need multiple "passes" on collision (more rare in my experience). \$\endgroup\$
    – ashes999
    Commented Dec 31, 2012 at 22:13
  • 1
    \$\begingroup\$ That is what I said, steps 1-4 are all together. There should be no drawing between these steps. I usually have a method called TryMovePlayer(x, y). \$\endgroup\$ Commented Dec 31, 2012 at 22:46
0
\$\begingroup\$

You were right, but I finally figured out a more advanced way (And amazingly rock solid) way of detecting and responding to a tile based level. (This code assumes the players and all tiles are 16x16, and their corresponding origins are all (x0, y0))

First, I declare these variables at the start of the player's update method;

            // The middle points of the 4 edges of the players bounding rectangle
        topMiddle = new Vector2(pos.X + rect.Width / 2, pos.Y);
        bottomMiddle = new Vector2(pos.X + rect.Width / 2, pos.Y + rect.Height);
        leftMiddle = new Vector2(pos.X, pos.Y + rect.Height / 2);
        rightMiddle = new Vector2(pos.X + rect.Width, pos.Y + rect.Height / 2);

        // The distance on one axis, reset and re-used after each direction check
        int distanceToPlayer = 0;

        // The index in the list of tiles of the closest tile to the player
        // The one above,
        int nearestObjectU = 0;

        // the one below,
        int nearestObjectD = 0;

        // the one to the left,
        int nearestObjectL = 0;

        // and the one to the right. 
        int nearestObjectR = 0;

I then check for each tile, one direction at a time;

            #region Check Top

        foreach (Tile t in tileList)
        {
            if (t.rect.Y + 1 < pos.Y && topMiddle.X + 7 >= t.rect.X && topMiddle.X - 7 < (t.rect.X + t.rect.Width))
            {
                if (distanceToPlayer == 0)
                {
                    nearestObjectU = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.Y - t.rect.Y);
                }
                else if (Math.Abs(pos.Y - t.rect.Y) < distanceToPlayer)
                {
                    nearestObjectU = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.Y - t.rect.Y);
                }

            }
        }

        tileList[nearestObjectU].color = Color.Red;
        closestUp = tileList[nearestObjectU];
        distanceToPlayer = 0;

        #endregion
        #region Check Bottom

        foreach (Tile t in tileList)
        {
            if (t.rect.Y - 1 > pos.Y && topMiddle.X + 7 >= t.rect.X && topMiddle.X - 7 < (t.rect.X + t.rect.Width))
            {
                if (distanceToPlayer == 0)
                {
                    nearestObjectD = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.Y - t.rect.Y);
                }
                else if (Math.Abs(pos.Y - t.rect.Y) < distanceToPlayer)
                {
                    nearestObjectD = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.Y - t.rect.Y);
                }

            }
        }

        tileList[nearestObjectD].color = Color.Red;
        closestDown = tileList[nearestObjectD];
        distanceToPlayer = 0;

        #endregion
        #region Check Left

        foreach (Tile t in tileList)
        {
            if (t.rect.X + 1 < pos.X && leftMiddle.Y + 7 >= t.rect.Y && leftMiddle.Y - 7 < (t.rect.Y + t.rect.Height))
            {
                if (distanceToPlayer == 0)
                {
                    nearestObjectL = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.X - t.rect.X);
                }
                else if (Math.Abs(pos.X - t.rect.X) < distanceToPlayer)
                {
                    nearestObjectL = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.X - t.rect.X);
                }

            }
        }

        tileList[nearestObjectL].color = Color.Red;
        closestLeft = tileList[nearestObjectL];
        distanceToPlayer = 0;

        #endregion
        #region Check Right

        foreach (Tile t in tileList)
        {
            if (t.rect.X > pos.X - 1 && rightMiddle.Y + 7 >= t.rect.Y && rightMiddle.Y - 7 < (t.rect.Y + t.rect.Height))
            {
                if (distanceToPlayer == 0)
                {
                    nearestObjectR = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.X - t.rect.X);
                }
                else if (Math.Abs(pos.X - t.rect.X) < distanceToPlayer)
                {
                    nearestObjectR = tileList.IndexOf(t);
                    distanceToPlayer = (int)Math.Abs(pos.X - t.rect.X);
                }

            }
        }

        tileList[nearestObjectR].color = Color.Red;
        closestRight = tileList[nearestObjectR];
        distanceToPlayer = 0;
        #endregion

Thus, the player now holds the tile in each of the 4 directions on the X and Y axes closest to him, and can be used in the Main update accordingly. You may have noticed that for each direction, when it had found the closest tile, it painted it Color.Red. This was so I could visually see which tiles it was selecting. (The tile classes update method automatically changes its color back to white before this is called, so only the four adjacent ones are red.) And then, in the main update method, you simply keep the player from moving past these points, like so;

                    #region Apply Gravity

                if (playerPos.Y < player.closestDown.rect.Y - 16)
                {
                    if (playerSpeed.Y < 0 && playerSpeed.Y > -0.5f)
                        playerSpeed.Y = 0.2f;

                    if (playerSpeed.Y < 0)
                    {
                        playerSpeed.Y += 0.2f;
                    }
                    else
                    {
                        playerSpeed.Y += 0.3f;
                    }
                }
                else
                {
                    playerSpeed.Y = 0;
                    playerPos.Y = player.closestDown.rect.Y - 16;
                }

                if (!(playerPos.Y > player.closestUp.rect.Y + 16))
                {
                    playerPos.Y = player.closestUp.rect.Y + 16;
                    playerSpeed.Y = 1f;
                }


                #endregion

                #region Keep in bounds

                if (!(playerPos.X > player.closestLeft.rect.X + 16))
                {
                    playerSpeed.X = 0;
                    playerPos.X = player.closestLeft.rect.X + 16;
                }

                if (!(playerPos.X < player.closestRight.rect.X - 16))
                {
                    playerSpeed.X = 0;
                    playerPos.X = player.closestRight.rect.X - 16;
                }



                #endregion

                #region Proccess Input

                if (kbs.IsKeyDown(Keys.Up) && pkbs.IsKeyUp(Keys.Up))
                {

                        playerSpeed.Y -= 6f;

                }
                if (kbs.IsKeyDown(Keys.Down))
                {

                }
                if (kbs.IsKeyDown(Keys.Left))
                {
                    if (playerPos.X > player.closestLeft.rect.X + 16)
                    {
                        if (playerSpeed.X == 0)
                        {
                            playerSpeed.X -= 1;
                        }
                        if (playerSpeed.X > -5.7f)
                        {
                            playerSpeed.X += -0.3f;
                        }
                    }
                    else
                    {
                        playerSpeed.X = 0;
                        playerPos.X = player.closestLeft.rect.X + 16;
                    }
                }
                if (kbs.IsKeyDown(Keys.Right))
                {
                    if (playerPos.X < player.closestRight.rect.X - 16)
                    {
                        if (playerSpeed.X == 0)
                        {
                            playerSpeed.X += 1;
                        }
                        if (playerSpeed.X < 5.7f)
                        {
                            playerSpeed.X += 0.3f;
                        }
                    }
                    else
                    {
                        playerSpeed.X = 0;
                        playerPos.X = player.closestRight.rect.X - 16;
                    }
                }

                if (kbs.IsKeyUp(Keys.Right) && kbs.IsKeyUp(Keys.Left))
                {
                    if (playerSpeed.X > 0.1f)
                    {
                        playerSpeed.X -= 0.35f;
                    }
                    if (playerSpeed.X < -0.1f)
                    {
                        playerSpeed.X += 0.35f;
                    }

                    if (Math.Abs(playerSpeed.X) < 0.2f)
                    {
                        playerSpeed.X = 0;
                    }
                }
            }

                #endregion

It makes sure that the player can't go past those points, and double checks when trying to move. I was surprised at how solid it was once I managed to finish it.

And yes, I know the code is poorly commented. It should be fairly straight forward anyways.

That article on 2D platformers gave me the idea on how to code this. It's so freaking solid, I'm going to make a tutorial on how to implement this type of collision response.

\$\endgroup\$

You must log in to answer this question.

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