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

Started by
28 comments, last by zuhane 8 years, 4 months ago

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

                    }


                }

            }


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

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

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.

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.

Okay, but are you generating contact lists?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I don't know what you mean by this. What is a contact list?

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?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

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?

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).

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

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?

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

This topic is closed to new replies.

Advertisement