# 2D tile-based collisions: using different methods by tile?

I'm trying to write a collision handling method that handles each of the 9 potentially colliding tiles (the player's and the 8 around him) differently according to a "CollisionType" value attached to each tile, the goal being a system that allows a player to walk on jagged slopes or objects smaller than a tile width, but only doing per-pixel checks on tiles that actually need it--plain, square tiles just get the bounding box treatment.

This is my first attempt at cobbling together a collision detection system that isn't a blatant clone of tutorial code. In fact, I've yet to find any code online that works like this, leading me to believe that it's probably a terrible idea--which is why I'm going to post the skeleton of what I'm working on here (thoroughly commented) and ask if the way I'm doing this makes any sense at all.

public void xHandleCollisions()
{
/*
* Preparations. Find out where the entity is, what tiles are around
* it, its collision box, and where it's trying to go.
*/
//get the cell the entity is in
Vector2 currentLoc = LevelManager.CurrentMap.GetCellByPixel(worldLocation);

//from that, find the range of indexes to check for collision--
//entity can't possibly touch anything it's not adjacent to,
//unless it's moving more than one tile per frame, which I can deal with later
//if it ever actually happens.
int startX = (int)MathHelper.Clamp(currentLoc.X - 1, 0, LevelManager.CurrentMap.MapWidth);  //[o][o][o]-1
int endX = (int)MathHelper.Clamp(currentLoc.X + 1, 0, LevelManager.CurrentMap.MapWidth);    //[o][x][o]
int startY = (int)MathHelper.Clamp(currentLoc.Y - 1, 0, LevelManager.CurrentMap.MapHeight); //[o][o][o]+1
int endY = (int)MathHelper.Clamp(currentLoc.Y + 1, 0, LevelManager.CurrentMap.MapHeight);    //-1    +1

//now, create rectangles representing the entity's current collision box
//and its desired new position.
Rectangle oldPosition = CollisionRectangle; //<--Thus far, any attempt at
//changing this from being just
//the sprite's frame has ended
//with bizarre errors.
Rectangle newPosition = CollisionRectangle;
newPosition.Offset((int)velocity.X, (int)velocity.Y);

//don't need these anymore this frame, zero them out for next frame
velocity.X = 0;
velocity.Y = 0;

//if there's no difference, just return
if (oldPosition.Equals(newPosition)) return;

//for each of the rectangles, get four vectors for per-pixel terrain collisions
//   ___x___    <--Head, for colliding with tiles above
//  |       |
//  |       |
//  x       x   <--for determining horizontal collisions and
//  |___x___|       how steep a slope can be climbed
//      ^--for determining collisions with ground

//old
Vector2 oldFeet = oldPosition.GetBottomCenter();
Vector2 oldLeft = new Vector2(
oldPosition.GetBottomCenter().X - (oldPosition.Width / 2),
oldPosition.GetBottomCenter().Y - (oldPosition.Height / 10));
Vector2 oldRight = new Vector2(
oldPosition.GetBottomCenter().X + (oldPosition.Width / 2),
oldPosition.GetBottomCenter().Y + (oldPosition.Height / 10));

//new
Vector2 newFeet = newPosition.GetBottomCenter();
Vector2 newLeft = new Vector2(
newPosition.GetBottomCenter().X - (newPosition.Width / 2),
newPosition.GetBottomCenter().Y - (newPosition.Height / 10));
Vector2 newRight = new Vector2(
newPosition.GetBottomCenter().X + (newPosition.Width / 2),
newPosition.GetBottomCenter().Y + (newPosition.Height / 10));

/*
* Now, the actual collision detection. Calculated differently for each tile
* depending upon what collision type the tile has, but each resulting in an
*/
Tile.CollisionType cType;
//Check horizontal collisions with all tiles,
//adjusting newPosition as little as possible.
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
cType = LevelManager.CurrentMap.GetTileAtCell(x, y).GetCollisionType;//<---

switch (cType)
{
case Tile.CollisionType.None:
//do nothing
break;
case Tile.CollisionType.Solid:
//adjust x movement to not enter this tile.
//first, get the collision depth between the tile and newPosition.
Vector2 collisionDepth =
RectangleExtensions.GetIntersectionDepth(newPosition, LevelManager.CurrentMap.CellWorldRectangle(x, y));
//if its x is zero, we can skip it--there's no collision with this tile.
if ((int)collisionDepth.X != 0)
{
//if there's a collision, adjust newPos so there isn't,
//but only in the x direction for now
newPosition.Offset((int)collisionDepth.X, 0);
}
break;
case Tile.CollisionType.Platform:
//x dimension, so do nothing
break;
case Tile.CollisionType.PerPixel:
//do per pixel collision detection, checking
//points against tile collision layer so player
//can move on uneven terrain or stand on objects
//smaller than a full tile width

break;
}

}
}

//Check vertical collisions with all tiles, adjusting newPosition as little as possible.
// Treat Platforms as impassable if they're located below oldFeet, otherwise passable.
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
cType = LevelManager.CurrentMap.GetTileAtCell(x, y).GetCollisionType;

switch (cType)
{
case Tile.CollisionType.None:
//do nothing
break;
case Tile.CollisionType.Solid:
//adjust y movement to not enter this tile
//first, get the collision depth between the tile and newPosition.
Vector2 collisionDepth =
RectangleExtensions.GetIntersectionDepth(newPosition, LevelManager.CurrentMap.CellWorldRectangle(x, y));
//if its y is zero, we can skip it--there's no collision with this tile.
if ((int)collisionDepth.Y != 0)
{
//if there's a collision, adjust newPos so there isn't,
//but only in the y direction
newPosition.Offset(0, (int)collisionDepth.Y);
}
break;
case Tile.CollisionType.Platform:
//if oldFeet.y >= platform height, treat as solid
//else, do nothing

break;
case Tile.CollisionType.PerPixel:
//do per pixel collision detection, checking
//points against tile collision layer

break;
}

}
}

//At this point, newPosition should be the best valid movement. Adjust worldLocation
//to match the coordinates of newPosition's origin.
worldLocation.X = newPosition.X;
worldLocation.Y = newPosition.Y;
//done?
}


So...

Question: Logically speaking, is this feasible? Or is there some massive flaw in the process that I've so far been unable to see?

Second Question: If this approach is too terrible to save, is there another, better way to accomplish the same thing? I don't want to limit the game to a few predetermined slope angles, and I'd like the character to be able to stand on an object that's far less than the full width of a tile (and not float in the air next to it, either).

I'm aware that some of the stored vectors are currently pointless (most of the old* ones other than oldFeet, for instance); I'll cull them out later if I don't find a use for them. And yes, I know the code for solid tiles is wrong already--the character falls from his spawn location, lands on the ground correctly, but then can't move at all.

There doesn't seem to be anything inherently wrong with the method of your collision detection code here.

It really depends on how you are going to do to check the Tile.CollisionType.PerPixel collision type, as this will really want to be high performance.

The fact that you are trying to be as simple as possible in only checking the most basic collision type you can is a good thing. The same thing happens in 3D:

Bounding Sphere => Smaller Spheres within => Face Collision checks

To possibly make the code more future proof, and maybe a more readable (smaller methods are nicer), you could have each tile as an interface with a collision method.

class ITile { bool CheckCollision(...); };

class PerPixelTile : ITile { bool CheckCollision(...) { // check pixel collision here... } };

• I'm still thinking about exactly how to handle the per-pixel collisions. I'll probably kill horizontal movement if the forward side sensor collides with a solid pixel, check if Y velocity is positive or negative, then either align the head sensor to the pixel above it (Y<0) or align the foot sensor to the pixel below it (Y>0). I'm sure I'm missing half a dozen considerations, but I guess I'll find out tomorrow night. Commented Feb 25, 2013 at 7:01
• @Sasquatch be sure to let us know how it goes then! Commented Feb 25, 2013 at 7:04