The example provided by Microsoft seems as if the collision detection (from what I can see) will have a small error. When the user collides with an Unpassable tile, the depth of the intersection is calculated. The smaller of the depth values X and Y is used to fix the position of the user so it no longer collides with the tile. But if the user was travelling diagonally could this result in the user not ending up in precisely the point where the character would first collide with the tile?
I'm probably wrong but that's just the way I see it.
private void HandleCollisions()
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = BoundingRectangle;
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
// If this tile is collidable,
TileCollision collision = Level.GetCollision(x, y);
if (collision != TileCollision.Passable)
{
// Determine collision depth (with direction) and magnitude.
Rectangle tileBounds = Level.GetBounds(x, y);
Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
// Resolve the collision along the shallow axis.
if (absDepthY < absDepthX || collision == TileCollision.Platform)
{
// If we crossed the top of a tile, we are on the ground.
if (previousBottom <= tileBounds.Top)
isOnGround = true;
// Ignore platforms, unless we are on the ground.
if (collision == TileCollision.Impassable || IsOnGround)
{
// Resolve the collision along the Y axis.
Position = new Vector2(Position.X, Position.Y + depth.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
else if (collision == TileCollision.Impassable) // Ignore platforms.
{
// Resolve the collision along the X axis.
Position = new Vector2(Position.X + depth.X, Position.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
Answer
You are absolutely right. I've had my share of problems with the collision routines on the XNA platformer sample. But I've managed to start from the code as provided in the sample, and modified it a bit until I achieved consistent results in every test scenario I could throw at it.
In particular, the sort of problem I was having was when trying to slide along a wall by moving diagonally against it. Because of the assumption the sample makes in order to resolve collisions based on the smallest axis of displacement, this resulted in the character not being able to move when pushing against a wall in some direction. For instance, using one sign, I would get stuck when hugging the ceiling and trying to move against it from left to right (can't remember the specifics). Switching the sign would solve that situation but a problem would appear in the opposite scenario. Bottom line is that with the provided implementation I couldn't get it to work correctly in all sides and from every direction - it would always fail on at least one case.
So the core of the changes I did were all about starting to handle movement on the X axis independently from movement on the Y axis, on two separate steps. I've written about it before in this answer so head there for the details.
And if I remember correctly, the actual reason for it was something like this:
No comments:
Post a Comment