2D Platformer Tile Collision And Gravity

Started by
4 comments, last by robert_porter 14 years, 1 month ago
Hey, I'm having some trouble implementing collision detection with gravity in a simple mario-esque 2d platformer I'm making. I have no problem detecting collisions, but it's how to deal with them that I'm struggling with. I can succesfully block tiles if I run into the left or right side of them. However when it comes to walking across tiles, and falling when none are there, I struggle. I know I need some kind of 'gravity' velocity to constantly push on my character, and then I can simply react to it when a collision is detected. However, this doesn't seem to work, as I currently just do in the inverse of the velocity when a collision is detected. But this leads my character to becoming 'stuck' and unable to move, and the gravity then moves them down again in the next update... My Collision Method:

void cCharacterPlayer::UpdateCollisions()
		////Check collisions against tiles - all tiles for now - will improve
		for (int row = 0; row < MAPSIZE_Y; row++) 
		{
			for (int col = 0; col < MAPSIZE_X; col++) 
			{
				//No point checking against tiles that aren't visible
				if (m_gameState->GetMap()->GetTile(sVector2(col, row)).GetDrawableSet() == true )
				{
					//Intersect character rectangle with tile rectangle
					if (  GetCharacterRectangle().Intersects(m_gameState->GetMap()->GetTile(sVector2(col, row)).GetCollisionRect()) == true )
					{

						//Gems
						if (m_gameState->GetMap()->GetTile(sVector2(col, row)).GetType() == gem)
						{
							//Set player score

							//Change to blank tile
							m_gameState->GetMap()->GetTile(sVector2(col, row)).UnsetDrawable();


						}
						else
						//Must be a solid tile
						{
							//Undo the last move
							m_position.x -= m_velocity.x; 
							m_position.y -= m_velocity.y;
						}

					}
				}
			}
		}



My rectangle checking:

	bool sRectangle::Intersects(const sRectangle& _other) const
	{
		return !(left > _other.right || right < _other.left ||
			top > _other.bottom || bottom < _other.top);
	}


Totally stuck! Thanks! James
Advertisement
Heya!

I've hit this problem in the past too. A SIMPLE solution i would do (I'm sure other people will recomend other things, but this is a simple way to fix it) is:

#1 - instead of trying to move the object along both X and Y at the same time, break it up into 2 move/tests. Try and move along the X amount. If it fails, reverse it. Then, Try to move along the Y amount. If it fails, reverse it.

What that will do is let the player rest on the ground while letting them still run left and right.

Also this will let you fall, even though your body may be pushing up against a vertical wall.

#2 - when your y velocity is such that it is pushing you downwards, and the y velocity movement step from #1 fails (ie hits something), set a flag bPlayerIsOnGround to true, else set it to false.

What that will do is give you a flag to tell you when you are on the ground or not. Then, to implement jumping, all you have to do is say... if they press the jump key, and they are on the ground, set their y velocity to -10 (or whatever number makes sense in your game).

There are more "propper" ways to fix these things (ie rigid body physics / simulations, and people will undoubtably recomend you use a physics library like havok or perhaps bullet physics), but "rolling your own" has good benefits so try out these changes and keep forging on your own path IMO (:

It's a good learning experience.
Oh also... why are you looping through your whole map to see what tiles the player has collided against?

That is really ineficient and as your map gets larger it's going to become a bigger and bigger bottleneck to your preformance (if it isn't already).

Instead of looping through the whole map and seeing if the player intersects with each tile, what you should do is figure out which tiles the player overlaps (there will be a minimum x tile and a maximum x tile and a minimum y tile and a maximum y tile) and just loop through those guys to see if he is overlapping anything important.

You can calculate those guys by doin some pretty simple math based on the player's position on your tile map (:
Thanks! I'll look into doing the seperate tests. And at the moment, I'm doing 5 assignments to finish my degree, so figure it's better to have something than nothing (plus one of my tasks for this assignment is to show profiling and optimisations - such as an inefficient and more efficient collision tests).
oh cool deal, good luck man!!
This is how I did it for a side scroller I made a long time ago.

The intersection of 2 boxes is the same as the intersection of 1 box shrunk to a point and another box inflated by the first boxes size. This is simple to prove, just write an intersection test with one box with min, max representation and another with center, extents represantion and you can see that you can add/subtract the extents.

So for a swept test this is the same as a ray and a box.

If you're moving right you will hit the left edge, left the right edge, down the top edge and up the bottom edge.

The time of intersection with an infinite edge will be
// note this is in 1d
t=(edge-pos)/displacement

and an intersection will happen when t is in the interval [0, 1] and an intersection with a box will happed when the intervals overlap. When the intervals don't overlap there is no intersection(txmin > tymax || tymax > txmin). The smallest t that in contained in the intersection of the intervals is the first collion.

Then you can move your guy only as far as the collion.

pos = pos + displacement * tmin

Then alter your displacement/velocity for whatever physical effects you want, and move for the rest of the fraction of t, doing all your collision test again.

pos = pos + tmin * displacement;
displacement = (1-tmin) * displacement
DoCollisions()


The way I designed it... assume i'm moving right and standing over 2 tiles, since I am moving right I would only check against the rightmost tile and not the left one, this helped get the correct response in corners. The maximum displacement was capped each frame to be no longer than the width/height of a grid element so that the max collisions possible against a regular grid was never more than 3, and only 3 when you walked into a corner.(Drawing a picture makes it make more sense).

For gravity and bouncing both, just don't bounce anything moving less than some minimal speed. simply zero it's displacment/velocity in that direction

if(Dot(displacenent, normal) < epsilon))
dispalcement -= Dot(displacement, normal) * normal

This topic is closed to new replies.

Advertisement