Search code examples
c++sfml

C++ Sfml, How can I create a collision box for my Sprite


I have a question to sfml.

I am relative new to C++ and sfml. I am trying to create a Space Invaders type of game. I currently have some problems with collision, between the enemy's bullets and the rocket, I'm talking about line 145. This line:

if (collide(rocket, enemy_bullets[i]))
{
    window.close();
}

Can you create something like a collision box?, because I don't want to collide with the whole rocket sprite, I only want to collide with parts of it, e.g not the transparent parts.

#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <chrono>

void print(std::string string)
{
    std::cout << string << std::endl;
}

sf::CircleShape create_bullet(sf::Vector2f possition, sf::Int16 offset)
{
    sf::CircleShape circel;
    circel.setRadius(10);
    circel.setPosition(possition.x + offset, possition.y);
    return circel;
}

bool collide(sf::Sprite a, sf::CircleShape b)
{
    return a.getGlobalBounds().intersects(b.getGlobalBounds());
}

int main()
{
    int speed;
    speed = 25;

    sf::RenderWindow window(sf::VideoMode(1200, 800), "Space Invaders", sf::Style::Titlebar | sf::Style::Close);
    sf::Texture rocket_texture;
    if (!rocket_texture.loadFromFile("data/rocket.png"))
    {
        print("Problem with loding file data/rocket.png");
        exit(-1);
    }

    sf::Texture enemy_texture;
    if (!enemy_texture.loadFromFile("data/enemy.png"))
    {
        print("Problem with loding file data/enemy.png");
        exit(-1);
    }

    sf::Sprite rocket;
    sf::Sprite enemy;

    std::chrono::milliseconds couldown = std::chrono::milliseconds(0);
    std::chrono::milliseconds time;

    std::chrono::milliseconds enemy_couldown = std::chrono::milliseconds(0);
    bool enemy_fire = false;
    float bulletspeed = 0.02;

    // sf::CircleShape test = create_bullet();

    int changex;

    rocket.setTexture(rocket_texture);
    rocket.setPosition(500, 650);
    rocket.scale(0.5, 0.5);

    std::vector<sf::Sprite> enemy_list;
    std::vector<sf::CircleShape> player_bullets;
    std::vector<sf::CircleShape> enemy_bullets;

    enemy.setTexture(enemy_texture);
    enemy.scale(0.2, 0.2);

    for (int i =0; i<8; i++)
    {
        enemy.setPosition(i * 150, 400);
        enemy_list.push_back(enemy);
    }


    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            changex = 0;
            switch (event.type)
            {
            // window closed
            case sf::Event::Closed:
                window.close();
                break;

            // key pressed
            case sf::Event::KeyPressed:
                if (event.key.code == sf::Keyboard::A)
                {
                    if (rocket.getPosition().x >= 0 )
                    {
                        changex = changex - speed;
                    }
                }
                else if (event.key.code == sf::Keyboard::D)
                {
                    if (rocket.getPosition().x <= 1100)
                    {
                        changex = changex + speed;
                    }
                }
                else if (event.key.code == sf::Keyboard::Space)
                {
                    time = std::chrono::duration_cast<std::chrono::milliseconds>(
                        std::chrono::system_clock::now().time_since_epoch());
                    if (couldown < time - std::chrono::milliseconds(100)){
                        couldown = time;
                        player_bullets.push_back(create_bullet(rocket.getPosition(), 47));
                    }                    
                }
                
                break;

            default:
                break;
            }
            rocket.move(changex, 0);
        }

        window.clear();
        window.draw(rocket);
        //swindow.draw(test);
        for (int i=0; i<player_bullets.size();i++)
        {
            player_bullets[i].move(0,-bulletspeed);
            window.draw(player_bullets[i]);
            if (player_bullets[i].getPosition().y < 0)
            {
                player_bullets.erase(player_bullets.begin()+i);
            }
        }

        for (int i = 0; i < enemy_bullets.size(); i++)
        {
            enemy_bullets[i].move(0, bulletspeed);
            window.draw(enemy_bullets[i]);
            if (enemy_bullets[i].getPosition().y > 800)
            {
                enemy_bullets.erase(enemy_bullets.begin() + i);
            }
            if (collide(rocket, enemy_bullets[i]))
            {
                window.close();
            }
        }

        time = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch());
        if (enemy_couldown < time - std::chrono::milliseconds(2000))
        {
            enemy_couldown = time;
            enemy_fire = true;
        }

        // Draw all Enemys
        for (int i = 0; i < enemy_list.size(); i++)
        {
            for (int j = 0; j < player_bullets.size(); j++)
            {
                if (collide(enemy_list[i], player_bullets[j]))
                {
                    enemy_list.erase(enemy_list.begin() + i);
                }
            }

            if (enemy_fire)
            {
                enemy_couldown = time;
                // ADD: Move enemys
                enemy_bullets.push_back(create_bullet(enemy_list[i].getPosition(), 13));
            }
            window.draw(enemy_list[i]);
        }
        enemy_fire = false;
        window.display();
    }

    return 0;
}

If you have any idea how to do that, I would like to hear it.

Thanks, in advance


Solution

  • You can make a class that derives from sf::Sprite that has a sf::FloatRect for a hitbox, you will need to make a function to set the hitbox.

    class Sprite : public sf::Sprite {
        sf::FloatRect hitbox;
    }
    

    You can move the hitbox to the sprites location with:

    getTransform().transformRect(hitbox);
    

    I have used this in the past for hitboxes with SFML.


    Edit, Here is an full example program:

    #include <SFML/Graphics.hpp>
    
    /// custom sprite class with hitbox
    class HitboxSprite : public sf::Sprite {
    public:
        /// sets the hitbox
        void setHitbox(const sf::FloatRect& hitbox) {
            m_hitbox = hitbox;
        }
        /// gets the hitbox (use this instead of getGlobalBounds())
        sf::FloatRect getGlobalHitbox() const {
            return getTransform().transformRect(m_hitbox);
        }
    private:
        sf::FloatRect m_hitbox;
    };
    
    int main() {
        sf::RenderWindow window(sf::VideoMode(256, 128), "Example");
    
        // create two sprites, player and enemy
        HitboxSprite player;
        player.setPosition({ 64.f, 64.f });
        HitboxSprite enemy;
        enemy.setPosition({ 128.f, 64.f });
        enemy.setColor(sf::Color::Red);
        // create sprite texture and apply to sprites
        sf::Texture square_texture;
        square_texture.loadFromFile("32x32square.png");
        player.setTexture(square_texture);
        enemy.setTexture(square_texture);
        // set custom hitboxes
        // (this one starts (8, 8) pixels from the top left and has a size of (16, 16)
        // (this means the hitbox will be 1/2 of the square in the middle)
        player.setHitbox({ 8.f, 8.f, 16.f, 16.f });
        enemy.setHitbox({ 8.f, 8.f, 16.f, 16.f });
    
        sf::Clock clock;
        while (window.isOpen()) {
            // process events
            sf::Event event;
            while (window.pollEvent(event)) {
                if (event.type == sf::Event::Closed)
                    window.close();
            }
    
            const float dt = clock.restart().asSeconds();
            constexpr float player_speed = 128.f;
            // move player with arrow keys
            player.move({
                player_speed * dt * (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) - sf::Keyboard::isKeyPressed(sf::Keyboard::Left)),
                player_speed * dt * (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) - sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            });
    
            // check for collision
            const bool colliding = player.getGlobalHitbox().intersects(enemy.getGlobalHitbox());
    
            // set background color based on collision
            window.clear(colliding ? sf::Color::Green : sf::Color::Blue);
            // draw sprites
            window.draw(enemy);
            window.draw(player);
            // display
            window.display();
        }
    
        return 0;
    }
    

    If you need any part explained let me know. Here is the translucent png I made with the center part being the hitbox: 32x32square