Search code examples
c++sfmlcollision

Collisions not working properly (Making the player get stuck)


I can't get the collisions just right. The player just gets stuck in the blocks, even when on top of them. (There is gravity btw)

My collisions rely on a "Past" position for the player

Edited: I made a mistake in the code, so I changed it.

Here is my code:

#include <SFML/Graphics.hpp>
#include <iostream>
#include <math.h>
constexpr auto PI = 3.14159265;
bool rr(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2) {
    return (x1 + w1 > x2 && y1 + h1 > y2 && x1 < x2 + w2 && y1 < y2 + h2);
};
//#include <string>
int main()
{
    
    //Player stuffs
    sf::Vector2f playerPos(0, 0);
    sf::Vector2f playerPastPos(0, 0);
    sf::Vector2f playerVel(0, 0);
    sf::RectangleShape playerRender;
    
    playerRender.setSize(sf::Vector2f(20, 20));
    playerRender.setFillColor(sf::Color(0, 255, 0));

    sf::RenderWindow window(sf::VideoMode(600, 400), "Platformer (SFML)");
    window.setFramerateLimit(60);
    sf::Vector2u windowSize = window.getSize();
    int block_map[] = {
        0,0,0,0,0,0,0,1,
        0,0,0,0,0,0,0,1,
        0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,2,
        1,0,0,0,0,0,1,1,
        1,1,1,1,1,1,1,1,
    };
    int map_width = 8;
    int map_height = 8;
    sf::RectangleShape rect[sizeof(block_map) / sizeof(*block_map)];
    
    for (int i = 0; i < sizeof(block_map) / sizeof(*block_map); i++) {
            
            rect[i].setPosition(sf::Vector2f((i%(map_width))*20, std::floor(i/map_height)*20));
            rect[i].setFillColor(sf::Color(255, 255, 255, 0));
            switch(block_map[i]){
                case 1:
                    rect[i].setFillColor(sf::Color(255, 255, 255));
                    break;
                case 2:
                    rect[i].setFillColor(sf::Color(255, 0, 255));
                    break;
            }
            
            //std::cout <<"/"+std::to_string((a[i].at(j)));
            
            
        
        
        
    }
    
    int i = 0, j = 50, l = 0;
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        windowSize = window.getSize();
        playerPastPos = playerPos;
        while (l < sizeof(rect) / sizeof(rect[0])) {
            
            rect[l].setSize(sf::Vector2f(20, 20));

            //rect[l].setPosition(i, j);

            window.draw(rect[l]);

            //i += 25;

            l++;
        }
        for (int i = 0; i < sizeof(block_map) / sizeof(*block_map); i++) {
            bool canColl = false;
            sf::Vector2f pos = sf::Vector2f((i % (map_width)) * 20, std::floor(i / map_height) * 20);


            if (rr(playerPos.x, playerPos.y, 20, 20, pos.x, pos.y, 20, 20)) {
                switch (block_map[i]) {
                case 1:
                    canColl = true;
                    break;
                case 2:
                    canColl = true;
                    break;
                }
                //0.0174532925
                if (canColl) {
                    
                        
                    if (playerPastPos.y < pos.y - 20) {
                        playerVel.y = 0;
                        playerPos.y = pos.y - 20;
                    }
                    if (playerPastPos.x > pos.x + 20) {
                        playerVel.x = 0;
                        playerPos.x = pos.x + 20;
                    }
                    if (playerPastPos.y > pos.y + 20) {
                        playerVel.y = 0;
                        playerPos.y = pos.y + 20;
                    }
                    if (playerPastPos.x < pos.x - 20) {
                        playerVel.x = 0;
                        playerPos.x = pos.x - 20;
                    }
                    
                }
            }

        }
        //Player in game stuffs
        playerVel.x *= 0.95;
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D) && playerVel.x < 3) {
            playerVel.x += 0.5;
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A) && playerVel.x > -3) {
            playerVel.x -= 0.5;
        }
        playerVel.y += 0.3;
        playerPos += playerVel;
        
        
        playerRender.setPosition(playerPos);
        window.draw(playerRender);

        


        // Reset variables.
        l = 0;
        i = 0;
        // Copy the buffer to the window.
        window.display();
    }
}

Solution

  • Note that the origin of your rectangle shapes is on their top left corner by default. This means that the pos.x and pos.y values you have when checking for collisions are the top left corner of your blocks. This is also true of your player if it inherits sf::Transformable. With that in mind, your calculations are all wrong. You should have:

    bool rr(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2) {
        // Is player pos x inside block pos x?
        // Yes if top right x of player is greater than top left x of block
        // and if top left x of player is lesser than top right x of block at the same time
        bool xIn = (x1 + w1 > x2) && (x1 < x2 + w2);
        
        // Is player pos y inside block pos y?
        // Yes if top left y of player is greater than bottom left y of block
        // and if bottom left y of player is lesser than top left y of block at the same time
        bool yIn = (y1 > y2 - h2) && (y1 - h1 < y2);
    
        return xIn && yIn;
    };
    

    Now that you know you have a collision, you have to correct the player's position. Now your implementation is hard to follow and could be improved. I'll try to change that :

    // Correct x pos
    // Is player coming from left or right?
    if ((playerVel.x > 0) && (playerPastPos.x + 20 < pos.x)) {
        // Coming from left
        playerVel.x = 0;
        playerPos.x = pos.x - 20;
    }
    else if ((playerVel.x < 0) && (playerPastPos.x > pos.x + 20)) {
        // Coming from right
        playerVel.x = 0;
        playerPos.x = pos.x + 20;
    }
    
    // Correct y pos
    // Is player coming from top or bottom?
    if ((playerVel.y > 0) && (playerPastPos.y < pos.y - 20)) {
        // Coming from bottom
        playerVel.y = 0;
        playerPos.y = pos.y - 20;
    }
    else if ((playerVel.y < 0) && (playerPastPos.y - 20 > pos.y)) {
        // Coming from top
        playerVel.y = 0;
        playerPos.y = pos.y + 20;
    }
    

    This is obviously not the best way to do things but I tried to follow the basic ideas of your implementation.