Zecksidd
Zecksidd

Reputation: 27

How to handle collision after a diagonal movement with rectangles

I'm trying to make a simple platformer but i have a problem with collision solving after a diagonal movement. I have a tile map with which i can iterate every tiles that are around of the player.

Here is the code to detect and handle collision :

static void CORE_ENTITY_CheckAroundTiles(entity_t *e)
{
    int tilex = (int)floor((e->x+(e->w/2))/TILE_SIZE);
    int tiley = (int)floor((e->y+(e->h)/2)/TILE_SIZE);

    tile_t *tile;

    for (int dx=tilex-1 ; dx<=tilex+1 ; dx++)
    {
        for (int dy=tiley-1 ; dy<=tiley+1 ; dy++)
        {
            // Test collision with map
            tile = MAP_GetTile(core->map, dx, dy);
            if (tile == NULL) // Tile out of range
                continue;

            switch (tile->type)
            {
                case TILETYPE_WALL:
                    if (CORE_ENTITY_IsCollideTile(e, tile))
                        CORE_ENTITY_ResolveTileCollision(e, tile);
                        // MAP_ShowTile(core->map, dx, dy);
                    break;

                case TILETYPE_EMPTY:
                    break;
            }
        }
    }

}

CORE_ENTITY_IsCollideTile() function :

static bool CORE_ENTITY_IsCollideTile(entity_t *e, tile_t *t)
{
    if (e->x >= t->x + t->w)
        return false;
    if (e->x + e->w <= t->x)
        return false;
    if (e->y >= t->y + t->h)
        return false;
    if (e->y + e->h <= t->y)
        return false;

    return true;
}

CORE_ENTITY_ResolveTileCollision() function :

static void CORE_ENTITY_ResolveTileCollision(entity_t *e, tile_t *t)
{
    if (e->dx < 0) {
        // comes from left
        e->x = t->x + t->w;
    } else if (e->dx > 0) {
        // comes from right
        e->x = t->x - e->w;
    }

    if (e->dy < 0) {
        // Comes from bottom
        e->y = t->y + t->h;
    } else if (e->dy > 0) {
        // Comes from top
        e->y = t->y - e->h;
        e->isOnGround = true;
    }
}

All of this works perfectly for vertical or horizontal movement, but the problem comes with diagonal movement.

For exemple :

when the player (in blue) stand here :

player

if i press right+bottom, the collision solving will replace the player at the top AND at the left of the tile, because it will have detected a movement from right. But i would like to replace the player on the top and apply the right movement. Can anyone give me a clue ?

Upvotes: 0

Views: 162

Answers (1)

Henry
Henry

Reputation: 11

I had a similar issue when I attempted to make a platformer game a little while ago.

The reason this is happening is because of the ResolveTileCollision function. In this function, you check the direction that the player is coming from, and reset their position based on that. This means that collision where e.dx is ANYTHING but 0 will collide in the x direction and be reset to the left of the object (this is the same for e.dy). Because of this, when the movement of the entity is a diagonal(both dx and dy are != 0), both the X and Y directions are reset.

The way I have worked around this bug is by using a different type of collision detection. I would read the "A posteriori (discrete) versus a priori (continuous)" section of this wikipedia article on types of collision.

Essentialy, a posteriori collision happens after(like the "a posteriori") the collision has happend, and then reset the objects position. a priori collision happens before this has happend, checks if the object will collide, and then stops them. By using a priori collision, you never have to adjust the player coordinates, because they havent collided with an object.

To implement this into your code, check if the entity is colliding in the X direction, and then change the entities dx to 0. By doing this, the entity will not collide in the X direction when their position is updated. Do the same for the Y.

psudocode (ish):

if (collides(entity.x + dx, entity.y, entity.w, entity.h))
{
    entity.dx = 0;
}
if (collides(entity.x, entity.y + dy, entity.w, entity.h))
{
    entity.dy = 0;
}
if (collides(entity.x + dx, entity.y + dy, entity.w, entity.h))
{
    // also check diagonal collision, AFTER checking horisontal and vertical. Do this so that you cannot clip into things moving diagonally, where you would not on just the x or y. (if youre wondering what that would be like, run it without this if statement, and sometimes youll clip into stuff diagonally. Its a cool and intuitive bug fix :) )
    entity.dx = 0;
    entity.dy = 0;
}

Implement sometime like this this however you want, but make sure that the DX and DY are checked induvidually and before checking both together.

Anyways Im very new to stackoverflow and just wanted to help someone out. Hopefully this is helpful. :)

Upvotes: 0

Related Questions