3
\$\begingroup\$

Before anyone say that this questions already has a answer, I will list all the questions I read and tried:

So, whats my problem? I am tring to implement a solid collision handling/response, but every time I try I fail. After fail with a lot of tile based solutions, like this one:

player.Y = tile.Y - player.height;

I am using the XNA Platformer Example as base, it is solid, but not completly. Some of the bugs is this:

XNA Platformer Example Bug

This happens very often when the character is even wider than from the example. I solved it changing this line:

if (absDepthY < absDepthX || collision == GameMap.TileCollision.Platform)

To this:

if (absDepthY < absDepthX || collision == GameMap.TileCollision.Platform || _velocity.X == 0)

So, if the player isn't moving in the X axis, don't resolve it (resolve the Y axis instead). It worked very well, but it only hides the problem. After some gameplay the error is evident when you fall down and is pushed some tiles to the right. See this example from my game, I slowed down the X velocity:

X bug

I can't walk to the left and if I fall on the middle of the tiles, I'm pushed to the right. In the game the velocity isn't too slow, but as I said, it still happens. This is the real problem.

Update

The problem above was solved using David's solution, now I need implement the one-way platforms. The code I have so far is this, I commented the most important parts:

private void HandleCollisions(Direction direction)
{
    Rectangle playerBounds = BoundingRectangle;
    int leftTile = playerBounds.Left / (int)GameMap.Instance.TileSize.X;
    int topTile = playerBounds.Top / (int)GameMap.Instance.TileSize.Y;
    int rightTile = (int)Math.Ceiling((float)playerBounds.Right / GameMap.Instance.TileSize.X) - 1;
    int bottomTile = (int)Math.Ceiling(((float)playerBounds.Bottom / GameMap.Instance.TileSize.Y)) - 1;

    _isOnGround = false;

    for (int y = topTile; y <= bottomTile; ++y)
    {
        for (int x = leftTile; x <= rightTile; ++x)
        {
            Vector2 depth;
            Rectangle tileBounds = GameMap.Instance.GetTileBounds(x, y);
            GameMap.TileCollision collision = GameMap.Instance.GetCollision(x, y);

            if (collision != GameMap.TileCollision.Passable && GameMap.Instance.TileIntersectsPlayer(playerBounds, GameMap.Instance.GetTileBounds(x, y), direction, out depth))
            {
                if (collision == GameMap.TileCollision.Platform && direction == Direction.Vertical && previousBottom < tileBounds.Top)
                {
                    // This is reachable if I fall on a platform tile only from the top!
                    _isOnGround = true;
                }

                if (collision == GameMap.TileCollision.Block || (direction == Direction.Vertical && _isOnGround))
                {
                    // The position is increased
                    Position += depth;
                    playerBounds = BoundingRectangle;
                    // But the player falls through the platform block.
                }
            }
        }
    }

    previousBottom = playerBounds.Bottom;
}

If I jump from bellow of the platform, when I reach the top of the tile the _isOnGround is set to true, so the condition bellow is executed and the player is supposed to be pushed to top, but he falls through the platform. I debugged and the Position is really increased, but it only happens one time. What am I doing wrong?

\$\endgroup\$
  • \$\begingroup\$ Have you tried to identify which side of the tiles the character is colliding with when falling down in-between tiles? It seems to me that when the problem happens, the collision being detected is between character and the sides of the tiles (or when it swaps directly trough the tile before collision gets detected, in which case you might need to implement continuous collision detection). \$\endgroup\$ – MAnd Feb 16 '16 at 20:08
  • \$\begingroup\$ @MAnd I solved the first problem, now I need implement the one-way platforms. \$\endgroup\$ – Rafael Almeida Feb 17 '16 at 1:49
  • \$\begingroup\$ Great. Now, I suggest that you include how did you solve the first part. Both because then it can help others in the future that happen to be looking into the same problem, and because that will make it much easier to help you in the other problem. Also, I recommend that you better explain what you want to achieve, i.e. what do you mean by "one-way platform", since that was the part with least details in the question \$\endgroup\$ – MAnd Feb 17 '16 at 2:28
  • \$\begingroup\$ @MAnd I solved the problem by myself after a long time, I will edit the question with the solution. \$\endgroup\$ – Rafael Almeida Feb 17 '16 at 3:07
  • \$\begingroup\$ No, you should post the solution as an answer. This is a Q and A site, take advantage of it ;) \$\endgroup\$ – Alexandre Vaillancourt Feb 17 '16 at 3:27
4
\$\begingroup\$

The first problem where the collisions were handled wrong, were solved with this solution posted by David. Instead of solving the two axis at the same time, the solution solves each axis individually. So, instead of sum the velocity at the position and handle the collisions, I sum each velocity and handle the direction:

if (_velocity.Y != 0f)
{
    Position += _velocity.Y * Vector2.UnitY * elapsed;
    Position = new Vector2(Position.X, (float)Math.Round(Position.Y));
    HandleCollisions(Direction.Vertical);
}

if (_velocity.X != 0f)
{
    Position += _velocity.X * Vector2.UnitX * elapsed;
    Position = new Vector2((float)Math.Round(Position.X), Position.Y);
    HandleCollisions(Direction.Horizontal);
}

To solve the second problem, I need manage a way to make the one-way platformers (the ones where you can jump from bottom but stop if falls from top). To resolve this, I made just like the XNA Platformer Demo. On the begin of HandleCollisions(), I reseted the _isOnGround variable to false, so at the beggin of verification the player is not on the ground.

If the tile collision that is being verified through the loop is not passable and the player is intersecting it, verify if the tile collision is a Platform, if the movement is vertical (because the one-way platforms ignores the x axis), and if the previous bottom of the player is less or equal of the tile top:

if (collision == TileCollision.Platform && direction == Direction.Vertical && previousBottom <= tileBounds.Top)
{
    _isOnGround = true;
}

If so, the player is on the ground, setting _isOnGround to true. After that, I check if the tile collision is a block, if is, the player needs be pushed out from the tile, but I also verify if the player is on the ground, that means he landed on a platform, so he also need be pushed out.

if (collision == TileCollision.Block || (direction == Direction.Vertical && _isOnGround))
{
    Position += depth;
    playerBounds = BoundingRectangle;
}

Before the end of the method, I set previousBottom = playerBounds.Bottom;, to prepare for the next update loop. The full method:

private void HandleCollisions(Direction direction)
{
    Rectangle playerBounds = BoundingRectangle;
    int leftTile = playerBounds.Left / (int)GameMap.Instance.TileSize.X;
    int topTile = playerBounds.Top / (int)GameMap.Instance.TileSize.Y;
    int rightTile = (int)Math.Ceiling((float)playerBounds.Right / GameMap.Instance.TileSize.X) - 1;
    int bottomTile = (int)Math.Ceiling(((float)playerBounds.Bottom / GameMap.Instance.TileSize.Y)) - 1;

    _isOnGround = false;

    for (int y = topTile; y <= bottomTile; ++y)
    {
        for (int x = leftTile; x <= rightTile; ++x)
        {
            Vector2 depth;
            Rectangle tileBounds = GameMap.Instance.GetTileBounds(x, y);
            GameMap.TileCollision collision = GameMap.Instance.GetCollision(x, y);

            if (collision != GameMap.TileCollision.Passable && GameMap.Instance.TileIntersectsPlayer(playerBounds, GameMap.Instance.GetTileBounds(x, y), direction, out depth))
            {
                if (collision == GameMap.TileCollision.Platform && direction == Direction.Vertical && previousBottom <= tileBounds.Top)
                {
                    _isOnGround = true;
                }

                if (collision == GameMap.TileCollision.Block || (direction == Direction.Vertical && _isOnGround))
                {
                    Position += depth;
                    playerBounds = BoundingRectangle;
                }
            }
        }
    }

    previousBottom = playerBounds.Bottom;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ +1 I really appreciate the time you toke to post such a detailed answer with how you did solve your problems. That will make a nice asset for future users who search for a similar problem. \$\endgroup\$ – MAnd Feb 17 '16 at 4:15
1
+50
\$\begingroup\$

If I understand correctly, your movement issue can be resolved by David's answer, but you want to implement being able to move upwards through platforms. This can be solved with an additional if statement. When you collide with a platform, check the players Y velocity and the target block type to see if you can move upwards through it. If the player's Y velocity is set above 0, or the block type cannot be moved through upwards, then resolve the collision as normal.

\$\endgroup\$
  • \$\begingroup\$ This doesn't make sense for me. If I remove the + depth.Y, how the player will be pushed out of the tile? And I don't need know which side the player need be pushed out, because depth can be negative or positive. \$\endgroup\$ – Rafael Almeida Feb 16 '16 at 20:13
  • \$\begingroup\$ Sorry Rafael, I misunderstood what the example RectangleExtensions did, and what your problem was. I've rewrote my answer to correspond with this. \$\endgroup\$ – ChrisUC Feb 16 '16 at 20:39
  • \$\begingroup\$ Yeah you are correct, use David's approach is the way to go. I can't make the one way platforms work, I'll update the question with the code I made so far. \$\endgroup\$ – Rafael Almeida Feb 17 '16 at 1:47
  • \$\begingroup\$ I figured how to make it. I'll give the points to you because I wasn't working with David's approach, you encouraged me to try it again (even if indirectly) and explained how to make the one-way platforms work. Thank you so much :) \$\endgroup\$ – Rafael Almeida Feb 17 '16 at 3:21
  • \$\begingroup\$ Obs.: Just review the concept of y velocity, because the velocity can be 0 but the player didn't reached the top of the platform tile, falling down over the tile but not from the top of it. \$\endgroup\$ – Rafael Almeida Feb 17 '16 at 3:24

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.