Hello there forum dwellers!! So I recently started thinking that there don't seem to be many tutorials out there which teach tile collision in a simple manner. The ones that do seem to skip out on one of the most important aspects: handling corners/edges. So I've been working on a platform game for quite some time now, and it uses a tile collision system which seems to do the job, but it also seems to have a few problems of its own.
I'd just like to be able to have a tile engine which detects where something is in relation to a tile, then adjusts its position accordingly. Currently, the tile system I have in place seems to work fine, but every now and again, some objects seem to slip through cracks in the tile, and I honestly can't figure out why. I've had this problem for a couple of years now, and it's always just gone on the backburner, because whenever I try and fix it, it just infuriates me! I have a few theories about what could possibly be causing it, but I'd really appreciate some insight from some more experienced programmers than myself!
Here's the general gist of how it works (in order):
1.) Game receives player input
2.) All game objects on screen update accordingly
3.) All game objects check for collisions with tiles
4.) All game objects re-position
It's also worth mentioning that the tiles are given properties as the level is loaded in. So if there's a strip of horizontal tiles, for example, only the tops and bottoms will check for collision, since nothing can fall inside of them and check for left/right collisions. Tiles surrounded by tiles are actually negated, since nothing can touch them, and perform no collision checks at all. An example of this is shown in this screenshot, where only the edges that use detection have a shadowed highlight around them:
As we see here, the strip of tiles under the player only check where collisions can happen. The strips to the left and right have no top/bottom collision checks.
As far is this goes, the collision works the majority of the time. However, sometimes things can fall through, such as bullets, shown here:
http://giphy.com/gifs/xTka01Lb9WeBuchFrq
Bullets and players inherit from the same class, so they use the same collision method. Here, we see the player's collision is handled fine until he hits a booster pad, which pushes him out of the map, even though his velocity is capped:
http://giphy.com/gifs/l0OWjxILsfxsbzLa0
The interesting thing about both of these is that the bullets and player only seem to be passing through the cracks in the tiles. I've noticed that most of the bullets fall through the tiles when either (bullet.x == tile.x) or ((bullet.x + bullet.width) == (tile.x + tile.width)), with the odd ones also falling through the middle tiles. So I guess there's no x penetration to take into account there. Also, the player passes through the wall when the base of him is touching the top of the tile ((player.y + player.height) == tile.y) or also when the top of him touches the underneath of a tile (player.y == (tile.y + tile.height)). This could just be coincidence, but it's an observation I've made.
Below is the code which every game entity uses to check against collidable tiles. If anyone could offer some insight, or even a more graceful solution, I would really appreciate it!
public void BoxCollision(CollisionTile currentTile)
{
GameSpace.collisionsWithHashing++;
Vector2 penetration;
Vector2 detection;
detection.X = (currentTile.Rectangle.Left + currentTile.Rectangle.Width / 2) -
(collisionRectangle.Left + collisionRectangle.Width / 2);
detection.Y = (currentTile.Rectangle.Top + currentTile.Rectangle.Height / 2) -
(collisionRectangle.Top + collisionRectangle.Height / 2);
if (Math.Abs(detection.X) > (collisionRectangle.Width / 2) + (currentTile.Rectangle.Width / 2) ||
Math.Abs(detection.Y) > (collisionRectangle.Height / 2) + (currentTile.Rectangle.Height / 2))
{
}
else
{
//Collision has occured.
penetration.X = (collisionRectangle.Width / 2) + (currentTile.Rectangle.Width / 2) - Math.Abs(detection.X);
penetration.Y = (collisionRectangle.Height / 2) + (currentTile.Rectangle.Height / 2) - Math.Abs(detection.Y);
if (penetration.Y < penetration.X)
{
//Touching top or bottom of platform
if (detection.Y < 0 && currentTile.bottomCollidable)
{
//Hitting underneath of platform
collidingWithBottom = true;
if (velocity.Y < 0)
{
if (elasticity > 0)
{
velocity.Y *= -elasticity;
}
else
{
velocity.Y = 0;
}
position.Y = currentTile.Rectangle.Bottom;
}
}
else if (detection.Y > 0 && currentTile.topCollidable)
{
//Hitting top of platform (walking and gravity bound)
collidingWithTop = true;
if (velocity.Y > 0)
{
if (elasticity > 0)
{
velocity.Y *= -elasticity;
}
else
{
velocity.Y = 0;
}
position.Y = currentTile.Rectangle.Top - collisionRectangle.Height;
}
}
}
else if (penetration.X < penetration.Y)
{
//Touching right or left of platform
if (detection.X < 0 && currentTile.rightCollidable)
{
//Hitting right side of platform
collidingWithRight = true;
if (velocity.X <= 0)
{
if (elasticity > 0)
{
velocity.X *= -elasticity;
}
else
{
velocity.X = 0;
}
position.X = currentTile.Rectangle.Right;
}
}
else if (detection.X > 0 && currentTile.leftCollidable)
{
//Hitting left side of platform
collidingWithLeft = true;
if (velocity.X >= 0)
{
if (elasticity > 0)
{
velocity.X *= -elasticity;
}
else
{
velocity.X = 0;
}
position.X = currentTile.Rectangle.X - collisionRectangle.Width;
}
}
}
}
}