0
\$\begingroup\$

I am currently working on a small 2D Games Engine with SDL2 for fun. The engine works in a way, that you can define a tileset, which accesses the engine and allows you to place tile by tile on the predefined window and specify what type of tile you want to place. If it should have collision or if it should rotate. My bug occurs by the collision with the player.

I seperate the block i want the player to collide with into 8 blocks(4 side-blocks and 4 Corner-blocks).If my player now collides with an tile, the program checks,which part of the tile the player has collided with and then pushes the player in a certain direction dependend on the block of the tile it collided with.

This is the code for the collision:

int Editor::Collision(EDITOR_BODY body)
{
    int rect = 0;
    for (int i = 0; i < tile_type.size(); i++)
    {
        if (tile_type.at(i) == ITEM_HITBOX)
        {
            #define COL(a,b) (a.x+a.w>b.x&&a.x<b.x+b.w&&a.y+a.h>b.y&&a.y<b.y+b.h)

            SDL_Rect corner_ur, corner_ul, corner_dr, corner_dl, side_l, side_r, block_u, block_d;

            corner_ul = {
                tiles.at(i)->GetX(),
                tiles.at(i)->GetY(),
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight() / 8
            };
            corner_ur = {
                tiles.at(i)->GetX()+tiles.at(i)->GetWidth()*7/8,
                tiles.at(i)->GetY(),
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight() / 8
            };
            corner_dr = {
                tiles.at(i)->GetX()+tiles.at(i)->GetWidth()*7/8,
                tiles.at(i)->GetY()+tiles.at(i)->GetHeight()*7/8,
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight() / 8
            };
            corner_dl = {
                tiles.at(i)->GetX(),
                tiles.at(i)->GetY() + tiles.at(i)->GetHeight() * 7 / 8,
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight() / 8
            };
            side_r = {
                tiles.at(i)->GetX() + tiles.at(i)->GetWidth() * 7 / 8,
                tiles.at(i)->GetY() + tiles.at(i)->GetHeight()/ 8,
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight()*6 / 8
            };
            side_l = {
                tiles.at(i)->GetX() ,
                tiles.at(i)->GetY() + tiles.at(i)->GetHeight() / 8,
                tiles.at(i)->GetWidth() / 8,
                tiles.at(i)->GetHeight() * 6 / 8
            };
            block_u = {
                tiles.at(i)->GetX()+ tiles.at(i)->GetWidth()/ 8 ,
                tiles.at(i)->GetY(),
                tiles.at(i)->GetWidth()*6 / 8,
                tiles.at(i)->GetHeight() /2
            };

            block_d = {
                tiles.at(i)->GetX() + tiles.at(i)->GetWidth() / 8 ,
                tiles.at(i)->GetY()+ tiles.at(i)->GetHeight() / 2,
                tiles.at(i)->GetWidth() * 6 / 8,
                tiles.at(i)->GetHeight() / 2
            };
            if (COL(corner_ul, body))rect=1;
            if (COL(corner_dl, body))rect = 2;
            if (COL(corner_ur, body))rect = 3;
            if (COL(corner_dr, body))rect = 4;

            if (COL(side_r, body))rect = 5;
            if (COL(side_l, body))rect = 6;
            if (COL(block_u, body))rect = 7;
            if (COL(block_d, body))rect = 8;
            //Release the next comment for testing purposes..
            SDL_SetRenderDrawColor(setup->GetRenderer(), 255,0, 0, 255);
            SDL_RenderFillRect(setup->GetRenderer(), &corner_ur);
            SDL_RenderFillRect(setup->GetRenderer(), &corner_ul);
            SDL_RenderFillRect(setup->GetRenderer(), &corner_dr);
            SDL_RenderFillRect(setup->GetRenderer(), &corner_dl);

            SDL_SetRenderDrawColor(setup->GetRenderer(), 0,255,  0, 255);
            SDL_RenderFillRect(setup->GetRenderer(), &side_r);
            SDL_RenderFillRect(setup->GetRenderer(), &side_l);

            SDL_SetRenderDrawColor(setup->GetRenderer(), 0, 0,255,  255);
            SDL_RenderFillRect(setup->GetRenderer(), &block_u);
            SDL_RenderFillRect(setup->GetRenderer(), &block_d);


            SDL_SetRenderDrawColor(setup->GetRenderer(), 255, 255, 255, 255);
            if (COL(corner_ul, body))SDL_RenderFillRect(setup->GetRenderer(),&corner_ul);
            if (COL(corner_dl, body))SDL_RenderFillRect(setup->GetRenderer(), &corner_dl);
            if (COL(corner_ur, body))SDL_RenderFillRect(setup->GetRenderer(), &corner_ur);
            if (COL(corner_dr, body))SDL_RenderFillRect(setup->GetRenderer(), &corner_dr);

            if (COL(side_r, body))SDL_RenderFillRect(setup->GetRenderer(), &side_r);
            if (COL(side_l, body))SDL_RenderFillRect(setup->GetRenderer(), &side_l);
            if (COL(block_u, body))SDL_RenderFillRect(setup->GetRenderer(), &block_u);
            if (COL(block_d, body))SDL_RenderFillRect(setup->GetRenderer(), &block_d);


        }
    }


    return rect;
}

My problem ist, that when the player stands on the right coordinats, that he is paralell to a tile, but hits a corner block first, he is able to clip through an block.

Here is a picture, that shows the bug:

enter image description here

Maybe you can tell me how to avoid this Problem, or if there is a better way to handle such collisions

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Instead of showing you code, I will just outline an abstract way to perform AABB (axis aligned bounding box) vs AABB collision detection, that you can translate to any language. It may require some changing of how you store your character/tile data.

First, construct your AABB (player & tile):

cornersA = vec2[4]; 
halfwith = player.width/2;
halfheight = player.height/2;
cornersA[0] = player.position+vec2(-halfwidth, halfheight); //upper left corner
cornersA[1] = player.position+vec2(-halfwidth, -halfheight); //lower left corner
cornersA[2] = player.position+vec2(halfwidth, -halfheight); //lower right corner
cornersA[3] = player.position+vec2(halfwidth, halfheight); //upper right corner

Repeat for tile

Now that we have useful data, lets define the Cartesian axes that you will use to tell if they are colliding:

axes[0] = vec2(1.0,0.0);
axes[1] = vec2(0.0,1.0);

This may seem superfluous but, I will revisit this later.

Now that we have two AABB and two axes defined, we can perform the detection proper in pseudocode:

minA, minB = MAX_VALUE // A is player, B is tile
maxA, maxB = MIN_VALUE
for each axis loop:
    for each corner in cornersA loop:
        position = dot(axis, corner);
        if position < minA
            minA = position
        end if
        if position > maxA
            maxA = position
        end if
    end loop
    // repeat for tile
    if minA > maxB
        exit //no collision
    else if maxA < minB
        exit //no collision
    else if (maxA-minB) < (maxB - minA)
        //collision: perform a simple correction
        player.position+=axis * (maxA-minB);
        continue;
    else if (maxB-minA) < (maxA - minB)
        //collision: perform a simple correction
        player.position+=axis * (maxB-minA);
        continue;
    end if
end loop

This will detect and correct the players position on the x and y axes, should a collision take place.

The reason I stated the pseudocode as a loop through a set of axis, is that you could expand it to 3D very easily.

If you want to handle something more complex, like rotating (orientated) bounding boxes, you will need something a little smarter. I recommend looking into separating axes theorem for collision detection. It's not the fastest collision detection algorithm, but it is easy to understand and implement.

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

Find the origin of the player, like a circle in the center say 0.0 is the origin of the circle. With that your character is colliding and stopping within the collided region which is (0.0) of the source of the player. If that character is a circle then its outer radius shouldn't be hard to calculate, as you can get it with (x,y) using radius. What you have to do is do make a lot of collision checks number ranging from (x->x1,y->y1) and have it stop at that point if any object passes through it.

\$\endgroup\$

You must log in to answer this question.

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