Jump to content
  • Advertisement
Sign in to follow this  
zuhane

Simple 2D collision detection for a platform game (easiest solution possible using tile engine)

This topic is 947 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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:

 

sasdasdasd_zpsclwldjip.png

 

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;
                        }

                    }


                }

            }


        }
Edited by zuhane

Share this post


Link to post
Share on other sites
Advertisement

I'm not sure about your higher level strategy from this. Are you generating a list of contacts? It sounds like you may be in a situation where contact resolution for one object is overriding resolution for other objects.

 

It may just be a fencepost error though. You're doing:

 

if (penetration.Y < penetration.X) { /*some things happen*/ }

else if (penetration.X < penetration.Y) { /*some other things happen*/ }

 

Which has an obvious excluded case...

Share this post


Link to post
Share on other sites

Does the collisionRectangle contains the integrated velocity? If yes you need to use the prev position and project the movement against the tiles - otherwise it will never be stable for fast objects like bullets or jumppads. Or a much simpler solution, you limit the velocity to a certain amount so you never pass through tiles.

 

Another approach would be using real contact resolution and use a speculative contacts solver: this has the advantage of fixing the bullet-through-paper problems in a very simple way -> When the projected relative velocity is greater than the signed closest distance between the bodies/rectangles divided by the timestep, you just have to remove the velocity so that its just touching each other. Really easy, but powerful technique and is simple to implement when you dont need rotation dynamics - which you mostly dont need in 2D-Platformers.

 

If you need more informations, paul has a really great tutorial for this: http://www.wildbunny.co.uk/blog/2011/12/11/how-to-make-a-2d-platform-game-part-1/

And the best benefit of this you get "real" physics! So stacking and pushing of objects will work out-of-the-box ;-)

But there are two downsides of this technique which you need to keep in mind: Ghost collisions and internal edge issues can happen, but for a tilebased game this can be easily fixed.

Edited by Finalspace

Share this post


Link to post
Share on other sites

I'd just like to point out that the velocity isn't causing the problem. I've done some reading around the subject on how objects moving

at a high velocity can pass through the tiles without ever getting to perform a check, but this is happening with objects moving at a 

capped velocity, so it's not an issue to do with speed. I'll add this in later though!

 

As you've mentioned, I think it's more an issue to do with preferencing one side over another. I actually just did a test, Khatharr, to see

what would happen to objects if (penetration.x == penetration.y) by colouring the objects red, and the objects passing through the tiles

don't actually do this, so it must be triggering inside the top/bottom collision checker. 

 

I've also read up on the tutorial that you posted, Finalspace, but it would require a drastic overhaul of my already-existing code, which I'd rather not

do, as there's lots of little intricacies in there that I really can't afford to lose. I'd ideally like to try and fix up the code I've already written.

Share this post


Link to post
Share on other sites

When you test for collision, don't resolve in the testing phase. Generate a list of contacts for any collider that can respond to collisions (iow - not walls).

 

When an object intersects both the left wall and the floor, it should have two contacts in its list (assuming it hit nothing else). Once you've generated a list of contacts for everything you move through that list and resolve the situation, considering all the contacts together instead of trying to resolve them separately, which could result in missed responses. It also allows dynamic objects to bounce off one another accurately, since both of the objects register the contact before either of them try to resolve it.

 

A "contact" is just a struct that contains all of the relevant information about the collision, such as penetration and the surface normal of what was hit, etc.

 

Make sense?

 

 

Share this post


Link to post
Share on other sites

Ah, that makes a lot of sense! The only thing is that don't have any collision between moving objects. Objects will only ever

check collisions against tiles, and the tiles never move from where they are. Does this mean that a contact list would still be necessary?

Share this post


Link to post
Share on other sites

a contact list is a method for resolving multiple simultaneous collisions in a sensible manner. whether the collisions occur with moving or stationary objects is pretty irrelevant - other than the fact that stationary objects typically have no collision response.

 

its sounds like you may have edge case issues.

 

its also sounds like you might have lack of stepped movement issues, but it may just be edge case issues.

 

its also possible you have multiple simultaneous collisions that are not being resolved correctly due to sequential resolution vs simultaneous resolution (like via a contact list).

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

Ah, that makes a lot of sense! The only thing is that don't have any collision between moving objects. Objects will only ever

check collisions against tiles, and the tiles never move from where they are. Does this mean that a contact list would still be necessary?

Is it possible that, when you check and find a collision, then resolve that collision between an object and a tile, it will have created a new collision point between that object and another tile, but you've already iterated through that tile when it wasn't colliding BEFORE this check?

 

It doesn't sound like that can happen, especially if your tiles don't move either (no moving platforms, right?), but that's the kind of problems collision points fix.

 

Otherwise, I don't know what to suggest, except making a capture log of the location of the player, and when it's noted the player has left the playing field, you dump that log (file, console, whatever), and the locations of the tiles it should have collided with, and go through the data by hand and see if you can spot what happened.  I know it's clunky, but sometimes you have to do that kind of thing to find problems.  Or have you done that already?

Edited by BeerNutts

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!