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

Author Topic: Sprites turn into white boxes when deleting objects  (Read 891 times)

0 Members and 1 Guest are viewing this topic.

redacted

  • Newbie
  • *
  • Posts: 1
    • View Profile
Sprites turn into white boxes when deleting objects
« on: August 21, 2022, 11:59:50 pm »
New to C++, I'm creating a program that spawns "platforms" which move up and get deleted once they hit the screen's top.

I read the White Box problem and used new for the textures, but my sprites still turn white whenever an object from the vector is erased. However, they seem to regain their textures right after, which hasn't happened to me before.

#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <vector>

class Platform
{
private:
    sf::Sprite m_sprite;
    sf::Texture* m_texture;
public:
    Platform() : m_texture{ new sf::Texture }
    {
        if (!m_texture->loadFromFile("textures/platform.png"))
        {
            throw "image not found";
        }
        m_sprite.setTexture(*m_texture);
        m_sprite.setPosition(100, 1080);
    }

    Platform(const Platform& copy) : m_sprite{ copy.m_sprite }, m_texture{ new sf::Texture }
    {
        if (!m_texture->loadFromFile("textures/platform.png"))
        {
            throw "image not found";
        }
        m_sprite.setTexture(*m_texture);
    }

    sf::Sprite getSprite() { return m_sprite; }
    void moveUp() { m_sprite.move(0, -10.f); }
    ~Platform() { delete m_texture; }
};

int main()
{
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "SFML works!", sf::Style::Fullscreen);
    window.setFramerateLimit(60);

    std::vector<Platform> platforms;
    int timer{};

    while (window.isOpen())
    {
        sf::Event evnt;
        while (window.pollEvent(evnt))
        {
            if (evnt.type == sf::Event::Closed)
            {
                window.close();
            }
        }

        //Spawn platforms every second
        timer++;
        if (timer >= 60)
        {
            platforms.push_back(Platform{});
            timer = 0;
        }

        //Erase platforms that go off screen
        for (auto i{ platforms.begin() }; i < platforms.end(); )
        {
            i->moveUp();
            if (i->getSprite().getPosition().y < 0)
            {
                i = platforms.erase(i);
            }
            else
            {
                i++;
            }
        }
        window.clear();

        //Draw
        for (auto i{ platforms.begin() }; i < platforms.end(); )
        {
            window.draw(i->getSprite());
            i++;
        }
        window.display();
    }
    return 0;
}
 

mhn2

  • Newbie
  • *
  • Posts: 3
    • View Profile
Re: Sprites turn into white boxes when deleting objects
« Reply #1 on: August 24, 2022, 02:04:27 pm »
The problem was you were using a vector. In vectors if you remove any item except the last item, some of the items will get moved around in memory, which was causing the white box problem for you because you didn't overload the assignment operator.

To fix your issue there are 4 solutions:
1- Overload the assignment operator
2- Keep pointers to the platforms inside the vector instead, be careful about memory leaks
3- Use a list instead of a vector
4- Keep the texture in a static member so all of the platforms use the same texture

Also note that if you aren't planning to move the texture to a static member, then there is no need to keep it as a pointer, since white box problem happens if the texture has been deleted before the sprites that were using it, but if you aren't using a static member and you aren't keeping it as a pointer, it will never happen on accident in your case. Just be sure that in both the copy constructor and assignment operator you are copying the texture or reloading it properly.

In the code below I did a mix of the solutions 1 & 3 & 4. Hopefully this will help you with your problem.
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <list>

class Platform
{
private:
    // Since all of the platforms load the same file, I moved the texture to a static member so all of the platforms can use the same texture.
    static sf::Texture* m_texture;
    sf::Sprite m_sprite;

public:
    Platform()
    {
        m_sprite.setTexture(*m_texture);
        m_sprite.setPosition(100, 1080);
    }

    Platform(const Platform &copy)
    {
        // Just to be sure the sprite is pointing at the currect texture.
        m_sprite.setTexture(*m_texture);
        m_sprite.setPosition(copy.m_sprite.getPosition());
    }

    sf::Sprite getSprite()
    {
        return m_sprite;
    }

    void moveUp()
    {
        m_sprite.move(0, -10.f);
    }

    // It's how you should overload the assignment operator
    Platform& operator= (const Platform& copy)
    {
        // Just to be sure the sprite is pointing at the currect texture.
        m_sprite.setTexture(*m_texture);
        m_sprite.setPosition(copy.m_sprite.getPosition());
        return *this;
    }

    // Since there is no need to reload the texture on every constructor, I moved the loading action to a static function.
    // An alternative can be to have a static variable that counts how many platform objects currently exist, then load the texture if the value previously was 0 in the constructor, and unload the texture if the value was 1 in the deconstructor.
    // But that approach might cause rapid loading and unloading which can bring your game's fps down.
    static void loadTexture()
    {
        if (m_texture == nullptr) // prevents memory leak
        {
            m_texture = new sf::Texture;
            if (!m_texture->loadFromFile("textures/platform.png"))
                throw "image not found";
        }
    }

    // I also moved the unloading to a static function.
    static void unloadTexture()
    {
        if (m_texture != nullptr) // prevents memory leak
        {
            delete m_texture;
            m_texture = nullptr;
        }
    }
};

// Nothing special here, it's the way static members work in c++.
// We need this line to make sure its initial value is `nullptr` and to prevent the "Undefined reference to..." error
sf::Texture* Platform::m_texture = nullptr;

int main()
{
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "SFML works!", sf::Style::Fullscreen);
    window.setFramerateLimit(60);

    Platform::loadTexture(); // loading the platform texture here, since we are going to use platforms now

    // I changed the vector to list, since in vectors, if you delete something from anywhere other the end of the vector, it will move every object inside it in the memory, which was the reason you where experiencing the white box error.
    // On the other hand, you can delete anything in a list without having to move other elements, but in return you can't access a random element of it outside of a loop.
    // Check https://cplusplus.com/reference/list/list/ to understand how it works.
    std::list<Platform> platforms;
    int timer = 0;

    while (window.isOpen())
    {
        sf::Event evnt;
        while (window.pollEvent(evnt))
        {
            if (evnt.type == sf::Event::Closed)
            {
                window.close();
            }
        }

        // Spawn platforms every second
        timer++;
        if (timer >= 60)
        {
            platforms.push_back(Platform());
            timer = 0;
        }

        // Erase platforms that go off screen

        // It's how you can iterate through members of a list
        for (auto it = platforms.begin(); it != platforms.end()
        {
            it->moveUp();
            if (it->getSprite().getPosition().y < 0)
                it = platforms.erase(it);
            else
                it++;
        }

        // Draw
        window.clear();

        // It's how you can iterate through members of a list
        for (auto it = platforms.begin(); it != platforms.end(); it++)
            window.draw(it->getSprite());

        window.display();
    }

    Platform::unloadTexture();

    return 0;
}
« Last Edit: August 25, 2022, 12:26:58 pm by mhn2 »