How to stop having a white square when using a sprite vector with SFML 2?

Viewed 129

I'm trying to make a sprite vector but the first few elements always show a white square. The variables are contained in the TileRPG class to then be used in the main.cpp tile.cpp

void TileRPG::draw(int id, float x, float y, sf::RenderWindow& window) {
    m_sprite[id].setPosition(sf::Vector2f(x, y));
    window.draw(m_sprite[id]);
}


TileRPG::TileRPG() {
    std::ifstream file { "data/tileRPGList.rf" };
    std::string line{ "" }, access { "" };
    sf::Texture texture;
    sf::Sprite sprite;
    unsigned int i = 0;

    while (std::getline(file, line)) {
        m_texture.push_back(texture);
        m_sprite.push_back(sprite);
        access = "data/" + line;
        m_texture[i].loadFromFile(access);
        m_sprite[i].setTexture(m_texture[i]);
        i++;
    }
}

tile.hpp

class Tile {
protected:
    std::vector<sf::Texture> m_texture;
    std::vector<sf::Sprite> m_sprite;
};

class TileRPG : public Tile {
public:
    TileRPG();
    void draw(int id, float x, float y, sf::RenderWindow& window);
};
2 Answers

The problem

From the sf::Sprite::setTexture() documentation:

the sprite doesn't store its own copy of the texture, but rather keeps a pointer to the one that you passed to this function.

So, an sf::Sprite object doesn't store a copy of an sf::Texture object. By the way, this is related to The Flyweight Pattern, as it can be inferred from sf::Sprite's documentation:

The separation of sf::Sprite and sf::Texture allows more flexibility and better performances: indeed a sf::Texture is a heavy resource, and any operation on it is slow (often too slow for real-time applications). On the other side, a sf::Sprite is a lightweight object which can use the pixel data of a sf::Texture and draw it with its own transformation/color/blending attributes.

The m_texture data member in your code is a vector of sf::Texture objects, i.e., std::vector<sf::Texture>. Calling push_back() on it will reallocate the vector's internal buffer if it doesn't have enough capacity to store the sf::Texture object to insert. Therefore, the sf::Texture objects stored in an std::vector<sf::Texture> may end up being located in a different position in memory after calling push_back(). Since the sf::Sprite objects just keep pointers to sf::Texture objects, these pointers will end up pointing to the wrong location in memory if the sf::Texture objects have been reallocated somewhere else in memory.

Possible Solutions

You could simply call std::vector::reserve() to reserve enough memory upfront for the vector's internal buffer. The drawback to this approach is that you would ideally need to know in advance the number of sf::Texture objects your vector is going to store to avoid wasting memory.

Another approach would be to call sf::Sprite::setTexture() only after you have properly populated the std::vector<sf::Texture> container. This way, as you aren't going to further insert sf::Texture objects into the vector, it won't reallocate its internal buffer.

Lastly, an alternative approach regarding container would be to define m_texture as an std::vector<std::unique_ptr<sf::Texture>> instead. This way, if a reallocation of the vector's internal buffer happens, only the address of the std::unique_ptr<sf::Texture> objects are affected, not the addresses of the pointee objects, (i.e., not the addresses of the sf::Texture objects). You could also use boost::stable_vector for the same purpose. In this case, you would just define m_texture as boost::stable_vector<sf::Texture> instead.

Sprite::setTexture doesn't make a copy of the texture, but stores a pointer to it. This pointer is then invalidated once the vector of textures reallocates its storage in one of the subsequent push_backs.

Related