# 2D Tile Based Collision Detection

I was looking into tile collision detection and found this where David Gouveia explains a great way to get around the person's problem by separating the two axis. So I implemented the solution and it all worked perfectly from all the testes I through at it. Then I implemented more advanced platforming physics and the collision detection broke down. Unfortunately I have not been able to get it to work again which is where you guys come in :)!

I will present the code first:

    public void Update(GameTime gameTime) {
if(Input.GetKeyDown(Keys.A)) {
velocity.X -= moveAcceleration;
}
else if(Input.GetKeyDown(Keys.D)) {
velocity.X += moveAcceleration;
}

if(Input.GetKeyDown(Keys.Space)) {
if((onGround && isPressable) || (!onGround && airTime <= maxAirTime && isPressable)) {
onGround = false;
airTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
velocity.Y = initialJumpVelocity * (1.0f - (float)Math.Pow(airTime / maxAirTime, Math.PI));
}
}
else if(Input.GetKeyReleased(Keys.Space)) {
isPressable = false;
}

if(onGround) {
velocity.X *= groundDrag;
velocity.Y = 0.0f;
}
else {
velocity.X *= airDrag;
velocity.Y += gravityAcceleration;
}

velocity.Y = MathHelper.Clamp(velocity.Y, -maxFallSpeed, maxFallSpeed);
velocity.X = MathHelper.Clamp(velocity.X, -maxMoveSpeed, maxMoveSpeed);

position += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
position = new Vector2((float)Math.Round(position.X), (float)Math.Round(position.Y));

if(Math.Round(velocity.X) != 0.0f) {
HandleCollisions2(Direction.Horizontal);
}
if(Math.Round(velocity.Y) != 0.0f) {
HandleCollisions2(Direction.Vertical);
}
}

private void HandleCollisions2(Direction direction) {
int topTile = (int)Math.Floor((float)Bounds.Top / Tile.PixelTileSize);
int bottomTile = (int)Math.Ceiling((float)Bounds.Bottom / Tile.PixelTileSize) - 1;
int leftTile = (int)Math.Floor((float)Bounds.Left / Tile.PixelTileSize);
int rightTile = (int)Math.Ceiling((float)Bounds.Right / Tile.PixelTileSize) - 1;

for(int x = leftTile; x <= rightTile; x++) {
for(int y = topTile; y <= bottomTile; y++) {
Rectangle tileBounds = new Rectangle(x * Tile.PixelTileSize, y * Tile.PixelTileSize, Tile.PixelTileSize, Tile.PixelTileSize);
Vector2 depth;

if(Tile.IsSolid(x, y) && Intersects(tileBounds, direction, out depth)) {
if(direction == Direction.Horizontal) {
position.X += depth.X;
}
else {
onGround = true;
isPressable = true;
airTime = 0.0f;
position.Y += depth.Y;
}
}
}
}
}


From the code you can see when velocity.X is not equal to zero the HandleCollisions() Method is called along the horizontal axis and likewise for the vertical axis. When velocity.X is not equal to zero and velocity.Y is equal to zero it works fine. When velocity.Y is not equal to zero and velocity.X is equal to zero everything also works fine. However when both axis are not equal to zero that's when it doesn't work and I don't know why. I basically teleport to the left side of a tile when both axis are not equal to zero and there is a air block next to me.

Hopefully someone can see the problem with this because I sure don't as far as I'm aware nothing has even changed from what I'm doing to what the linked post's solution is doing.

Thanks.

I'll try to give a quick answer, assuming that you want to keep using the same technique I suggested on the other post (which by the way is not perfect, but in practice I haven't run into any noticeable problems yet in the context of a game).

Basically you changed the most important part of the code when you applied the velocity all at once to the position. What you need to do after calculating velocity, is to apply it in two steps to your position such as:

// Correct example
float elapsed = (float) gameTime.ElapsedGameTime.TotalSeconds;
if(velocity.X != 0f)
{
position += velocity.X * Vector2.UnitX * elapsed;
position = new Vector2((float)Math.Round(position.X), position .Y);
HandleCollisions2(Direction.Horizontal);
}
if(velocity.Y != 0f)
{
position += velocity.Y * Vector2.UnitY * elapsed;
position = new Vector2(position.X, (float)Math.Round(position.Y));
HandleCollisions2(Direction.Vertical);
}


As opposed to what you were doing, which was something like the following code (mirroring the same structure as the code above):

// Wrong example
float elapsed = (float) gameTime.ElapsedGameTime.TotalSeconds;
position += velocity * elapsed;
position = new Vector2((float)Math.Round(position.X), (float)Math.Round(position .Y));
if(velocity.X != 0f)
{
HandleCollisions2(Direction.Horizontal);
}
if(velocity.Y != 0f)
{
HandleCollisions2(Direction.Vertical);
}

• This worked awesome for fixing some problems with my collision. However, Do you know how to add platform tiles? I tried but they only support me while I'm moving – Cyral Oct 7 '12 at 17:29
• @Cyral Hm... It should be working in every case, even when you're standing still. Each turn, you apply gravity to the player, which moves him down a little. But then the collision system detects a collision with the floor and moves the player back up to the correct position, and you set the character's vertical velocity to 0 because a collision occurred. This works as long as your acceleration is not too big or the player is not falling so fast that he goes through the tile in a single frame, which you should limit anyway. – David Gouveia Oct 7 '12 at 17:38
• What I mean by platform, Is being able to jump through it, but not fall back down. – Cyral Oct 7 '12 at 19:02
• @Cyral Are those platforms static? If so it should not be much different from normal bricks. The only difference is that if the player is raising (i.e. positive vertical velocity) you skip the vertical collision detection for those tiles, so he can go through them. Then when he's falling, if a collision is found and the character's feet are close enough to the top of the platform, nudge him back there, otherwise let him keep falling. – David Gouveia Oct 7 '12 at 19:08

Is depth.X always positive? I only ask because maybe its pushing you into the object then you show up on the left size. Granted, that's usually a negative direction, but its worth checking.

If that's true you need to do "position.X -= depth.X" depending on which side you are on or fix Intersection.

The code looks alright, i'm going to assume it works, i don't code in XNA, so i don't know which objects are yours. The issue is that you're not checking which collision resolution is the smallest. If either x or y velocity is 0 then

    if(Math.Round(velocity.X) != 0.0f) {
HandleCollisions2(Direction.Horizontal);
}
if(Math.Round(velocity.Y) != 0.0f) {
HandleCollisions2(Direction.Vertical);
}


is going to sort it for you (since there's possible collision only along the vertical or horizontal axis). However, if both of those velocities are different than zero then the collision is going to get solved for the horizontal axis first, then there won't be any collision so the vertical collision function won't do anything.

If you want to fix this, check both vertical and horizontal collision and find the smallest displacement vector, then resolve collision.

If you want to do what David explained in this answer (which the answer you linked is based on) then you'll have to separate your x and y movement and collision checking. Right now, you're calling position += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds; which is not what David was advocating.