10
\$\begingroup\$

Currently, I have a platformer with tiles for terrain (graphics borrowed from Cave Story). The game is written from scratch using XNA, so I'm not using an existing engine or physics engine.

The tile collisions are described pretty much exactly as described in this answer (with simple SAT for rectangles and circles), and everything works fine.

Except when the player runs into a wall whilst falling/jumping. In that case, they'll catch on a tile and begin thinking they've hit a floor or ceiling that isn't actually there.

Image of character catching

In this screenshot, the player is moving right and falling downwards. So after movement, collisions are checked - and first, it turns out the player character is colliding with the tile 3rd from the floor, and pushed upwards. Second, he's found to be colliding with the tile beside him, and pushed sideways - the end result being the player character thinks he's on the ground and isn't falling, and 'catches' on the tile for as long as he's running into it.

I could solve this by defining the tiles from top to bottom instead, which makes him fall smoothly, but then the inverse case happens and he'll hit a ceiling that isn't there when jumping upwards against the wall.

How should I approach resolving this, so that the player character can just fall along the wall as it should?

\$\endgroup\$
3
  • \$\begingroup\$ Similar to this question? 2D Platformer AABB collision problems \$\endgroup\$
    – House
    Commented May 21, 2012 at 3:26
  • \$\begingroup\$ @Byte56 The question isn't the same, but its answer might help. \$\endgroup\$ Commented May 21, 2012 at 4:06
  • \$\begingroup\$ A note: I've had very little time in the past couple of weeks to experiment with the answers here. I've implemented world collision resolution as answered in the question @Byte56 linked, which has separate bugs at high velocities, and haven't yet been able to try out the answers to this question. I will try those when I can and either accept an answer when I can, or post my own if I resolve it myself. \$\endgroup\$ Commented Jun 17, 2012 at 11:37

8 Answers 8

9
\$\begingroup\$

The simplest, more fail-proof approach is to simply not check for collisions on hidden edges. If you have two wall tiles, one directly above the other, then the bottom edge of the upper tile and the top edge of the lower tile should not be checked for collision against the player.

You can determine which edges are valid during a simple pass over the tile map, storing outward edges as flags on each tile location. You can also just do this at runtime by checking adjacent tiles during collision detection.

There are more advanced versions of this same technique that make it easier and faster to support non-square tiles as well.

I recommend that you read the articles below. They're decent simple introductions to doing basic physics and collision detection in modern platformers (like Cave Story). If you're going for a more old-school Mario feel, there's a few more tricks to worry about, but most of them involve handling jumping and animations rather than collision.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

\$\endgroup\$
2
  • \$\begingroup\$ Can you clarify how to ignore some edges during collision? I usually see collisions being detected as the intersection of two rectangles (the player bounds and the tile bounds). When you say not to check collision against individual edges, does it mean that the collision algorithm is not a rectangle-rectangle intersection but something else? Your description makes sense, but I'm unsure what the implementation would look like. \$\endgroup\$ Commented Jun 11, 2012 at 16:21
  • \$\begingroup\$ Yeah, just do a rectangle-line collision. Only simplified because the line is always axis-aligned. You can just decompose your box-box check. \$\endgroup\$ Commented Jun 11, 2012 at 17:49
5
\$\begingroup\$

Here the order I would do things....

1) Save current X,Y of character

2) Move the X direction

3) Check all corner of character by getting the tile data and check if it is solid by doing the following: (X and Y is the character position)

  • for top left: floor(X / tileWidth), floor(Y / tileHeight);
  • for top right: floor((X + characterWidth) / tileWidth), floor(Y / tileHeight);
  • for bottom left: floor(X + / tileWidth), floor((Y + characterHeight) / tileHeight);
  • for bottom right: floor((X + characterWidth) / tileWidth), floor((Y + characterHeight) / tileHeight);

...to obtain correct tile data from map data

4) If any of the tiles are solid, then you need to force X to the best position possible by restoring the saved X and...

  • if you where moving left: X = floor(X / tileWidth) * tileWidth;
  • if you where moving right: X = ((floor((X + characterWidth) / tileWidth) + 1) * tileWidth) - characterWidth;

5) Repeat 2-4 using Y

If your character is taller than a tile, you need to check more tiles. To do this you need to loop and add tileHeight to the characterY position to obtain tile

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

If the character is wider than 1 block then do the same but replace characterY, characterHeight, tileHeight, e.t.c. with characterX, characterWidth, e.t.c.

\$\endgroup\$
2
\$\begingroup\$

What if you checked for the possibility of a collision as part of the player's movement method, and rejected movement which caused the player to slide into another object? This would prevent the player from getting 'inside' a block, and therefore he could not be 'on top' of the block below it.

\$\endgroup\$
1
\$\begingroup\$

I ran into almost the exact same problem in a game that I'm working on currently. The way I resolved it was to store the area of the overlapping portion when testing for collisions, and then handling those collisions based on their priority (largest area first), then double checking each collision to see if it had already been resolved by an earlier handling.

In your example, the area of the x-axis collision would be greater than the y-axis, so it would be handled first. Then before attempting to handle the y-axis collision, it would check and see that it is no longer colliding after being moved on the x-axis. :)

\$\endgroup\$
1
\$\begingroup\$

A simple, but not perfect, fix is to change the shape of the player collision box so that it is not a square. Cut off the corners to make him octagon-like or something similar. This way, when he collides with a tile like the ones in the wall, he will slide off of it and not get caught. This isn't perfect because you can still notice him catching on corners a little bit, but he won't get stuck and it is very easy to implement.

\$\endgroup\$
1
\$\begingroup\$

A very similar problem was covered in this tutorial: Introduction to Game Development. The relevant part of the video is at 1:44, explaining with diagrams and code snippets.

Notice 14-tilemap.py exhibits the clipping problem, but it is fixed in 15-blocker-sides.py

I can't exactly explain why the last tutorial example doesn't clip while your code does, but I think it has something to do with:

  • Conditional collision response based on the type of tile (which edges are actually active.)
  • The player is always 'falling.' Although the player seems to be resting on a tile, the player is actually repeatedly falling through the tile, then being pushed back to the top. (The self.resting member in the code is only used to check if the player can jump. Despite this I can't get the player to do a 'double' jump from a wall. Theoretically it might be possible, though)
\$\endgroup\$
0
\$\begingroup\$

Another method (which I answered the question Byte56 linked to) would be to check if the collision resolution puts the character in an empty spot or not. So in your problem, you get a collision from the inside rectangle's ceiling to move it up, which would be illegal (as you're still in collision with another tile). Instead you only move it if it you're moved into a free space (such as how the collision from the upper tile would move you to the left), and once you find that collision, you are done.

The answer I gave had code, but it became overly complicated. I'd keep track of all collisions within that timeframe and only take the one which lead to a free space. However if there were none, I think I resorted to resetting the character's position to its last position, however that's really ugly and maybe you would rather try implementing something like the original Mario where he just moves in one direction or something when no free space resolution is possible. Also you could sort that list of collision resolutions and only move to the freespace with the shortest distance (I think that solution would be the most preferable, although I didn't code for that).

\$\endgroup\$
0
\$\begingroup\$

I've only done3 SAT in 3D so maybe I'm not getting this right. If I understand your problem it could be due to getting contacts with a contact axis that should not be possible, resulting in player acting like there is a floor. One workaround could maybe be to use a circle shape for your caracter instead, then you will only get contact axes in the x-axis direction when hitting a wall.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .