Before anyone say that this questions already has a answer, I will list all the questions I read and tried:
- AABB collision resolution issues
- Trouble with AABB collision response and physics
- https://gamedev.stackexchange.com/questions/98323/hard-time-implementing-aabb-collision-into-a-platformer
- 2D Platformer AABB collision problems
- 2D Platformer Collision Handling
- Collisions not working as intended
- Checking Collisions In 2D Platformer With Tiles
- And a lot more...
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:
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:
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?