Search code examples
c++collisionsfml

SFML Axis independent collision


I've implemented tilemap collision into my game, it works but the problem comes when I'm colliding on one axis and trying to move on the other. I can't slide along the wall.

in Player.cpp

void Player::update(float delta, std::vector<Tile>& tiles) {
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) || sf::Keyboard::isKeyPressed(sf::Keyboard::Up) || sf::Joystick::getAxisPosition(0, sf::Joystick::Y) < -20) {
        newPos.y -= speed * delta;
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::A) || sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Joystick::getAxisPosition(0, sf::Joystick::X) < -20) {
        newPos.x -= speed * delta;
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::S) || sf::Keyboard::isKeyPressed(sf::Keyboard::Down) || sf::Joystick::getAxisPosition(0, sf::Joystick::Y) > 20) {
        newPos.y += speed * delta;
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::D) || sf::Keyboard::isKeyPressed(sf::Keyboard::Right) || sf::Joystick::getAxisPosition(0, sf::Joystick::X) > 20) {
        newPos.x += speed * delta;
    }

    sf::Vector2f oldPos = sprite.getPosition();
    move(delta, newPos);

    for (int i = 0; i < tiles.size(); i++) {
        if (Collision::PixelPerfectTest(sprite, tiles[i].sprite) && tiles[i].collision) {
            sprite.setPosition(oldPos);
            newPos = oldPos;
        }
    }
}

void Player::move(float delta, sf::Vector2f position) {
    sprite.setPosition(position);
}

In Collision.cpp

bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit) {
        sf::FloatRect Intersection;
        if (Object1.getGlobalBounds().intersects(Object2.getGlobalBounds(), Intersection)) {
            sf::IntRect O1SubRect = Object1.getTextureRect();
            sf::IntRect O2SubRect = Object2.getTextureRect();

            sf::Uint8* mask1 = Bitmasks.GetMask(Object1.getTexture());
            sf::Uint8* mask2 = Bitmasks.GetMask(Object2.getTexture());

            // Loop through our pixels
            for (int i = Intersection.left; i < Intersection.left + Intersection.width; i++) {
                for (int j = Intersection.top; j < Intersection.top + Intersection.height; j++) {

                    sf::Vector2f o1v = Object1.getInverseTransform().transformPoint(i, j);
                    sf::Vector2f o2v = Object2.getInverseTransform().transformPoint(i, j);

                    // Make sure pixels fall within the sprite's subrect
                    if (o1v.x > 0 && o1v.y > 0 && o2v.x > 0 && o2v.y > 0 &&
                        o1v.x < O1SubRect.width && o1v.y < O1SubRect.height &&
                        o2v.x < O2SubRect.width && o2v.y < O2SubRect.height) {

                        if (Bitmasks.GetPixel(mask1, Object1.getTexture(), (int)(o1v.x) + O1SubRect.left, (int)(o1v.y) + O1SubRect.top) > AlphaLimit &&
                            Bitmasks.GetPixel(mask2, Object2.getTexture(), (int)(o2v.x) + O2SubRect.left, (int)(o2v.y) + O2SubRect.top) > AlphaLimit)
                            return true;

                    }
                }
            }
        }
        return false;
    }

Solution

  • That's because your collision test is all or nothing. I would do extra collision tests to see if the x or y new position is valid or not, something like:

    if (tiles[i].collision && Collision::PixelPerfectTest(sprite, tiles[i].sprite))
    {
        sf::Vector2f checkPosX = newPos;
        sf::Vector2f checkPosY = newPos;
    
        checkPosX.y = oldPos.y;
        checkPosY.x = oldPos.x;
    
        sprite.setPosition(checkPosX);
    
        if (!Collision::PixelPerfectTest(sprite, tiles[i].sprite))
        {
            newPos = checkPosX;
        }
        else
        {
            sprite.setPosition(checkPosY);
    
            if (!Collision::PixelPerfectTest(sprite, tiles[i].sprite))
            {
                newPos = checkPosY;
            }
            else
            {
                sprite.setPosition(oldPos);
                newPos = oldPos;
            }
        }
    }
    

    As an aside, if you do test tiles[i].collision first you will skip the more expensive PixelPerfectTest() test for non-collision tiles due to the expression short-circuiting.