Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: A small problem with collision detection that I don't know how to correct  (Read 141 times)

0 Members and 1 Guest are viewing this topic.

RetroRain

  • Newbie
  • *
  • Posts: 9
    • View Profile
Below is the whole program.  It is a very small and simple program made to test collisions.

You have a player rectangle, and two block rectangles.  If the player collides with either of the blocks, based on the direction he is coming from, his x or y coordinate gets pushed back.

So, the collision works fine in that sense...

However, for instance, if you are moving up and hit a block from the bottom (holding the up key), then press and hold the left key (while still holding the up key), and then let go of the up key, the player gets pushed all the way to the right, past the block.

I know why this is happening, it's obvious.  It is simply executing the collision routine for when you press left.  But I put flags in, such as movingUp, movingDown, etc., to combat this, and yet it still happens.

Also, in the code below, where it checks the key and the direction you're moving in, I even tried checking false for the other three directions, and it still doesn't solve anything.

For instance:
(if (playerRect.getGlobalBounds().intersects(blockRect[i].getGlobalBounds()) && movingUp && !movingDown && !movingLeft && !movingRight)

Feel free to copy and paste this code, and compile it yourself to see what I am talking about.

The collision works, but I don't know what logic I need to stop this collision bug from happening.  You'd think the flags that I have would work, but they're not.


Does anyone know WHY this is happening?

Any help would be greatly appreciated.  Thank you for your time.



#include <SFML/Graphics.hpp>
using namespace sf;

int main()
{
    RectangleShape playerRect;
    RectangleShape blockRect[2];

    float x = 128;
    float y = 120;

    bool movingUp = false;
    bool movingDown = false;
    bool movingLeft = false;
    bool movingRight = false;

    playerRect.setSize(Vector2f(16,16));
    playerRect.setFillColor(Color::Yellow);
    playerRect.setPosition(x,y);

    blockRect[0].setSize(Vector2f(16,16));
    blockRect[0].setFillColor(Color::Blue);
    blockRect[0].setPosition(200,120);

    blockRect[1].setSize(Vector2f(16,16));
    blockRect[1].setFillColor(Color::Blue);
    blockRect[1].setPosition(128,60);

    RenderWindow window(VideoMode(256,240),"");

    while (window.isOpen())
    {
        // Process events
        Event event;
        while (window.pollEvent(event))
        {
            // Close window: exit
            if (event.type == Event::Closed)
                window.close();
        }

        if (Keyboard::isKeyPressed(Keyboard::Up))
        {
            y -= 0.01;
            movingUp = true;
            movingDown = false;
            movingLeft = false;
            movingRight = false;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Down))
        {
            y += 0.01;
            movingUp = false;
            movingDown = true;
            movingLeft = false;
            movingRight = false;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Left))
        {
            x -= 0.01;
            movingUp = false;
            movingDown = false;
            movingLeft = true;
            movingRight = false;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Right))
        {
            x += 0.01;
            movingUp = false;
            movingDown = false;
            movingLeft = false;
            movingRight = true;
        }

        for (int i = 0; i < 2; i++)
        {
            if (playerRect.getGlobalBounds().intersects(blockRect[i].getGlobalBounds()) && movingUp) y = blockRect[i].getPosition().y + 16;
            if (playerRect.getGlobalBounds().intersects(blockRect[i].getGlobalBounds()) && movingDown) y = blockRect[i].getPosition().y - 16;
            if (playerRect.getGlobalBounds().intersects(blockRect[i].getGlobalBounds()) && movingLeft) x = blockRect[i].getPosition().x + 16;
            if (playerRect.getGlobalBounds().intersects(blockRect[i].getGlobalBounds()) && movingRight) x = blockRect[i].getPosition().x - 16;
        }

        playerRect.setPosition(x,y);

        // Clear screen
        window.clear();

        // Draw the sprite
        window.draw(playerRect);
        window.draw(blockRect[0]);
        window.draw(blockRect[1]);

        // Update the window
        window.display();
    }

    return 0;
}
« Last Edit: June 11, 2019, 04:08:52 am by RetroRain »

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
I'd advice not to spend too much time with this collision algorithm (it's too simplistic and doesn't account for tons of cases).

Please read this until "Understanding the math" and you'll have everything you need to implement proper AABB collision which will work properly. :)

It's in Lua, but it's very easy to implement this in C++.
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log

RetroRain

  • Newbie
  • *
  • Posts: 9
    • View Profile
Thank you.  But before I take the time to read that article and understand everything in it, I managed to get my collision detection to work, without using the built-in SFML collision functions.

I simply did the math for the bounding box sides, and the way I'm doing the collision, is simply checking BEFORE the player comes into contact with the box.  If the player is GOING TO COLLIDE, then don't move.

And it works.

Except there is one small problem...

As you can see in the code below (in the keyboard section), I'm simply checking for a collision with one block.

If I use a loop to cycle through all of the block rectangles, the player will move too fast, and then be able to go through the blocks (a little bit slower, but it goes through them).

You can see it where I commented the loop code out in the keyboard section.

The blockTop[0], blockBottom[0], blockLeft[0], blockRight[0] were blockTop[ i ], blockBottom[ i ], blockLeft[ i ], and blockRight[ i ].


So my question is, is there a way to check the array of block rectangles in the keyboard section, without the loop making everything so fast?



#include <SFML/Graphics.hpp>
using namespace sf;

int main()
{
    RectangleShape playerRect;
    RectangleShape blockRect[2];

    float playerTop, playerBottom, playerLeft, playerRight;
    float blockTop[2], blockBottom[2], blockLeft[2], blockRight[2];

    float playerX = 128;
    float playerY = 200;

    float blockX[2] = {200, 128};
    float blockY[2] = {120, 60};

    playerRect.setSize(Vector2f(16,16));
    playerRect.setFillColor(Color::Yellow);
    playerRect.setPosition(playerX,playerY);

    blockRect[0].setSize(Vector2f(16,16));
    blockRect[0].setFillColor(Color::Blue);
    blockRect[0].setPosition(blockX[0],blockY[0]);

    blockRect[1].setSize(Vector2f(16,16));
    blockRect[1].setFillColor(Color::Blue);
    blockRect[1].setPosition(blockX[1],blockY[1]);

    RenderWindow window(VideoMode(256,240),"");
    window.setSize(Vector2u(512,480));

    while (window.isOpen())
    {
        // Process events
        Event event;
        while (window.pollEvent(event))
        {
            // Close window: exit
            if (event.type == Event::Closed)
                window.close();
        }

        playerTop = playerY;
        playerBottom = playerY + 16;
        playerLeft = playerX;
        playerRight = playerX + 16;

        for (int i = 0; i < 2; i++)
        {
            blockTop[i] = blockY[i];
            blockBottom[i] = blockY[i] + 16;
            blockLeft[i] = blockX[i];
            blockRight[i] = blockX[i] + 16;
        }

        if (Keyboard::isKeyPressed(Keyboard::Up))
        {
            //for (int i = 0; i < 2; i++) {
            if (playerTop - 0.01 <= blockBottom[0]
             && playerBottom > blockTop[0]
             && playerLeft < blockRight[0]
             && playerRight > blockLeft[0]) {} else playerY -= 0.01; //}
        }
        else if (Keyboard::isKeyPressed(Keyboard::Down))
        {
            //for (int i = 0; i < 2; i++) {
            if (playerBottom + 0.01 >= blockTop[0]
             && playerTop < blockBottom[0]
             && playerLeft < blockRight[0]
             && playerRight > blockLeft[0]) {} else playerY += 0.01; //}
        }
        else if (Keyboard::isKeyPressed(Keyboard::Left))
        {
            //for (int i = 0; i < 2; i++) {
            if (playerLeft - 0.01 <= blockRight[0]
             && playerRight > blockLeft[0]
             && playerTop < blockBottom[0]
             && playerBottom > blockTop[0]) {} else playerX -= 0.01; //}
        }
        else if (Keyboard::isKeyPressed(Keyboard::Right))
        {
            //for (int i = 0; i < 2; i++) {
            if (playerRight + 0.01 >= blockLeft[0]
             && playerLeft < blockRight[0]
             && playerTop < blockBottom[0]
             && playerBottom > blockTop[0]) {} else playerX += 0.01; //}
        }

        if (Keyboard::isKeyPressed(Keyboard::Escape)) window.close();

        playerRect.setPosition(playerX,playerY);

        // Clear screen
        window.clear();

        // Draw the sprite
        window.draw(playerRect);
        window.draw(blockRect[0]);
        window.draw(blockRect[1]);

        // Update the window
        window.display();
    }

    return 0;
}

Thank you for your help.

RetroRain

  • Newbie
  • *
  • Posts: 9
    • View Profile
I tried it this way, having a flag be checked if colliding with a block, and then moving based on that flag.

For whatever reason, and I don't know why, the for loop only works for one block.  One you can't collide with, and the other you can go right through.

If I change the subscript of the block to 0, it works for 0.  If I change it to 1, it works for 1.  But not both.  I just don't get it.

#include <SFML/Graphics.hpp>
using namespace sf;

int main()
{
    RectangleShape playerRect;
    RectangleShape blockRect[2];

    bool canMoveUp = true;
    bool canMoveDown = true;
    bool canMoveLeft = true;
    bool canMoveRight = true;

    float playerTop, playerBottom, playerLeft, playerRight;
    float blockTop[2], blockBottom[2], blockLeft[2], blockRight[2];

    float playerX = 128;
    float playerY = 200;

    float blockX[2] = {200, 128};
    float blockY[2] = {120, 60};

    playerRect.setSize(Vector2f(16,16));
    playerRect.setFillColor(Color::Yellow);
    playerRect.setPosition(playerX,playerY);

    blockRect[0].setSize(Vector2f(16,16));
    blockRect[0].setFillColor(Color::Blue);
    blockRect[0].setPosition(blockX[0],blockY[0]);

    blockRect[1].setSize(Vector2f(16,16));
    blockRect[1].setFillColor(Color::Blue);
    blockRect[1].setPosition(blockX[1],blockY[1]);

    RenderWindow window(VideoMode(256,240),"");
    window.setSize(Vector2u(512,480));

    while (window.isOpen())
    {
        // Process events
        Event event;
        while (window.pollEvent(event))
        {
            // Close window: exit
            if (event.type == Event::Closed)
                window.close();
        }

        playerTop = playerY;
        playerBottom = playerY + 16;
        playerLeft = playerX;
        playerRight = playerX + 16;

        for (int i = 0; i < 2; i++)
        {

            blockTop[i] = blockY[i];
            blockBottom[i] = blockY[i] + 16;
            blockLeft[i] = blockX[i];
            blockRight[i] = blockX[i] + 16;

            //player collides with bottom of block
            if (playerTop - 0.01 <= blockBottom[i]
             && playerBottom > blockTop[i]
             && playerLeft < blockRight[i]
             && playerRight > blockLeft[i]) canMoveUp = false; else canMoveUp = true;

            //player collides with top of block
            if (playerBottom + 0.01 >= blockTop[i]
             && playerTop < blockBottom[i]
             && playerLeft < blockRight[i]
             && playerRight > blockLeft[i]) canMoveDown = false; else canMoveDown = true;

            //player collides with right side of block
            if (playerLeft - 0.01 <= blockRight[i]
             && playerRight > blockLeft[i]
             && playerTop < blockBottom[i]
             && playerBottom > blockTop[i]) canMoveLeft = false; else canMoveLeft = true;

            //player collides with left side of block
            if (playerRight + 0.01 >= blockLeft[i]
             && playerLeft < blockRight[i]
             && playerTop < blockBottom[i]
             && playerBottom > blockTop[i]) canMoveRight = false; else canMoveRight = true;
        }

        if (Keyboard::isKeyPressed(Keyboard::Up))
        {
            if (canMoveUp) playerY -= 0.01;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Down))
        {
            if (canMoveDown) playerY += 0.01;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Left))
        {
            if (canMoveLeft) playerX -= 0.01;
        }
        else if (Keyboard::isKeyPressed(Keyboard::Right))
        {
            if (canMoveRight) playerX += 0.01;
        }

        if (Keyboard::isKeyPressed(Keyboard::Escape)) window.close();

        playerRect.setPosition(playerX,playerY);

        // Clear screen
        window.clear();

        // Draw the sprite
        window.draw(playerRect);
        window.draw(blockRect[0]);
        window.draw(blockRect[1]);

        // Update the window
        window.display();
    }

    return 0;
}

Elias Daler

  • Hero Member
  • *****
  • Posts: 570
    • View Profile
    • Blog
    • Email
Sorry, I doubt anyone is going to debug this code for you...
If you want to handle collision before it happens, check this out: https://blog.hamaluik.ca/posts/swept-aabb-collision-using-minkowski-difference/

Collision is a complex topic. People spent tons of time figuring it out. There's no point trying to implement your own collision algorithm, because it's likely you'll find problems with it in one case or another.
Tomb Painter, Re:creation dev | eliasdaler.github.io | @EliasDaler | Tomb Painter dev log