0
\$\begingroup\$

I've been trying for a couple of days to solve an issue I'm having with top-down 2D collisions, whenever I collide with tiles that have adjacent collidable tiles my player sticks to the wall, If I collide right or left I can't move up or down, the same for the Y axis, I can't move left or right when colliding from the top or bottom. Here's a picture to represent my issue a bit better:

Collision box problems pressing left and up

The gray boxes represent the collision area.

Here is my code:

            Rectangle tileRect = new Rectangle((int)tile.Position.X, (int)tile.Position.Y, tile.SourceRect.Width, tile.SourceRect.Height);
            Vector2 temporaryPlayerPosition = player.Sprite.Position - player.Velocity;
            if (player.CollisionBox.Intersects(tileRect))
            {
                if ((player.CollisionBox.Right >= tileRect.Left && player.CollisionBox.Right < tileRect.Right) ||
                    (player.CollisionBox.Left <= tileRect.Right && player.CollisionBox.Right > tileRect.Left))
                {
                    player.CollisionBox.X = (int)temporaryPlayerPosition.X;
                    player.Sprite.Position.X = temporaryPlayerPosition.X;
                }
                if ((player.CollisionBox.Bottom >= tileRect.Top && player.CollisionBox.Bottom < tileRect.Bottom) ||
                    (player.CollisionBox.Top <= tileRect.Bottom && player.CollisionBox.Top > tileRect.Top))
                {
                    player.CollisionBox.Y = (int)temporaryPlayerPosition.Y;
                    player.Sprite.Position.Y = temporaryPlayerPosition.Y;
                }
            }

If anyone could point me to the right direction I would be grateful!! Thanks again!

EDIT!!!

I changed my whole collision code so it does a forward check before handling the displacemente of the sprite, I still have the stickyness issue though. This is the new code:

Rectangle forwardCollision = new Rectangle((int)(player.CurrentPos.X + (player.Velocity.X*4)), (int)(player.CurrentPos.Y + (player.Velocity.Y*4)), player.Sprite.SourceRect.Width, player.Sprite.SourceRect.Height);
player.CanRunX = player.CanRunY = true;
foreach (TmxTile tile in tileList)
{
    tile.Update(gameTime);
    Rectangle tileRect = new Rectangle((int)tile.Position.X, (int)tile.Position.Y, tile.SourceRect.Width, tile.SourceRect.Height);
    if (forwardCollision.Intersects(tileRect) && SolidTileIds.Contains(tile.Id))
    {
        if (player.Velocity.X != 0)
            player.CanRunX = false;
        if (player.Velocity.Y != 0)
            player.CanRunY = false;
    }
}
foreach(DebugRectangle rectangle in rectangleList)
{
    rectangle.Update(gameTime);
}

if (player.CanRunX)
    player.Sprite.Position.X += player.Velocity.X;
else
    player.Sprite.Position.X -= player.Velocity.X;
if (player.CanRunY)
    player.Sprite.Position.Y += player.Velocity.Y;
else
    player.Sprite.Position.Y -= player.Velocity.Y;

player.CurrentPos = player.Sprite.Position;
player.CollisionBox.X = (int)player.Sprite.Position.X;
player.CollisionBox.Y = (int)player.Sprite.Position.Y;
player.DebugRectangle.Position.X = forwardCollision.X;
player.DebugRectangle.Position.Y = forwardCollision.Y;
\$\endgroup\$
0
\$\begingroup\$

You might think of it like a filter. You'll simply remove movement in any direction that is currently blocked. For example:

Vector2 deltaMovement = player.Velocity;

if(blockedRight && deltaMovement.x > 0)
    deltaMovement.x = 0; //Don't move XPlus

if(blockedLeft && deltaMovement.x < 0)
    deltaMovement.x = 0; //Don't move XMinus

if(blockedUp && deltaMovement.y > 0)
    deltaMovement.y = 0; //Don't move YPlus

if(blockedDown && deltaMovement.y < 0)
    deltaMovement.y = 0; //Don't move YMinus

MovePlayer(deltaMovement);

This simply looks for collisions in each orthogonal and zeros out any velocity in that direction if there is a collision there. Clearly you'll need to implement the blocked* properties yourself.

\$\endgroup\$
  • \$\begingroup\$ The complexity of your if statements is likely making this more difficult to debug as well, so I would recommend simplifying that at least. \$\endgroup\$ – MichaelHouse Sep 12 '17 at 18:23
  • \$\begingroup\$ I did try it before but I think I have to remake all my input handling for this so I'm gonna try other things before I remake it all, if I do need to I'll try your code though! Thanks! \$\endgroup\$ – Amirgem Sep 12 '17 at 21:20
0
\$\begingroup\$

You had the right idea, but in adding Velocity to your temp player movement early on it made it hard to undo so your if statements got clunky.

For the following we're assuming positive values of X velocity = going right and positive values of Y velocity = going up down.

Lets clean this up and adjust the logic a bit shall we:

// Store some helpful variables to make our code easy to read
Rectangle tileRect = new Rectangle((int) tile.Position.X, (int) tile.Position.Y, tile.SourceRect.Width, tile.SourceRect.Height);
Rectangle colBox = player.CollisionBox;

// Lets not worry about velocity yet, just store temp position for next frame
Vector2 nextPosition = player.Sprite.Position;

// Only need to affect the position values if colliding
if (colBox.Intersects(tileRect)) {

    // Some variables to make our condition more readable.
    var collidingOnLeft = colBox.Left <= tileRect.Right;
    var collidingOnRight = colBox.Right >= tileRect.Left;
    var collidingOnBottom = colBox.Bottom <= tileRect.Top;
    var collidingOnTop = colBox.Top >= tileRect.Bottom;

    // We're colliding on the left, so lets make sure 
    // we can't go any further left by limiting the 
    // X Velocity to a positive value.
    if (collidingOnLeft) {
        nextPosition.X += Math.Max(0, player.Velocity.X) 
    }

    // Same idea here but with negative values
    if (collidingOnRight) {
        nextPosition.X += Math.Min(0, player.Velocity.X) 
    }

    // Now we're limiting in the Y direction but same principle
    if (collidingOnBottom) {
        nextPosition.Y += Math.Min(0, player.Velocity.Y) 
    }

    if (collidingOnTop) {
        nextPosition.Y += Math.Max(0, player.Velocity.Y) 
    }
} else {
    // No collision so just add the velocity
    nextPosition.X += player.Velocity.X;
    nextPosition.Y += player.Velocity.Y;
}

// We're done - set the values
player.CollisionBox.X = (int) nextPosition.X;
player.CollisionBox.Y = (int) nextPosition.Y;
player.Sprite.Position.X = nextPosition.X;
player.Sprite.Position.Y = nextPosition.Y;

Hopefully this points you in the right direction.

\$\endgroup\$
  • \$\begingroup\$ Thanks! I'll give it a try! Actually positive Y is going down here since my coords start on the top left \$\endgroup\$ – Amirgem Sep 12 '17 at 20:06
  • \$\begingroup\$ I'm glad I put that comment in then! I'll edit and reverse it here to avoid confusion. \$\endgroup\$ – Tom 'Blue' Piddock Sep 12 '17 at 20:06
  • \$\begingroup\$ I did try it out, first I had to comment your else since that mad my player walk like 500 squares per second, which is extremely fast... Probably because I handle my player position on my player class and not here, commenting it fixed it. The problem is when colliding, instead of stopping the character it actually boosts it to the other side, as if it had jumped the wall, I'll fool around with the code to see if I get it fixed and if that works! EDIT: Setting the += Math.xxx to -= made the player stop, but it still sticks to the walls... \$\endgroup\$ – Amirgem Sep 12 '17 at 21:19
  • \$\begingroup\$ I also found out that when colliding in any direction all vars to check the direction become true, even when colliding with, say, the bottom part of a tile with your top part <- that still shows true for the collidingOnBottom check \$\endgroup\$ – Amirgem Sep 12 '17 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.