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 oldHead = oldPosition.GetTopCenter();
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 newHead = newPosition.GetTopCenter();
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
* adjustment (or no adjustment) to the newPos rectangle.
*/
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.