Search code examples
c++spritesfmlstdvector

Why textures don't display properly?


I have a project where my textures are not displayed inside window properly. Instead of textures it will always show blank white sprites. I cant find out what I'am doing wrong. Here is my code:

class header with vector holding textures

    class textures
    {
    public:
        sf::Texture dirt_Texture;
        sf::Texture grass_Texture;
        std::vector<sf::Texture> texturesVector;
        /*
             dirt = 0
            grass = 1
        */
    public:
        textures();

    sf::Texture getTextureByID(int id);



};

and .cpp file for it:

//constructor populate vector with textures
textures::textures()
{
    if (!dirt_Texture.loadFromFile("dirt.PNG"))
    {
        std::cout << "Error while loading texture.\n";
    }
    dirt_Texture.isSmooth();
    texturesVector.push_back(dirt_Texture);

    if (!grass_Texture.loadFromFile("grass.PNG"))
    {
        std::cout << "Error while loading texture.\n";
    }
    texturesVector.push_back(grass_Texture);
    std::cout << "Texture constructor has been called.\n";
}

sf::Texture textures::getTextureByID(int id)
{
    return texturesVector[id];
}

with child class sprite:

class sprites : public textures
{
private:
    //textures* textureClass;
    sf::Sprite sprite;
    std::vector<int> textureIDsVector;
public:
    sprites();
    ~sprites();

    int getVectorValueAtLine(int &line);
    void setVectorValueAtLineTo(int &line, int value);
    void updateTextureAtLine(int& line);
    sf::Sprite* getSprite();

};

where primary functions looks this in .cpp

sprites::sprites()
{
    std::vector<int> cubeIDsVector(100, 0);
    textureIDsVector = cubeIDsVector;
    //textureClass = new textures();
    std::cout << "Sprite constructor has been called.\n";
}

void sprites::updateTextureAtLine(int& line)
{
    switch (textureIDsVector[line])
    {
    case 0:
        //switcher = 0;
        sprite.setTexture(getTextureByID(0));
        sprite.setColor(sf::Color(55, 150, 150, 150));
        std::cout << "case 0\n";
        break;
    case 1:
        //switcher = 1;
        sprite.setTexture(getTextureByID(1));
        sprite.setColor(sf::Color(155, 50, 150, 150));
        std::cout << "case 1\n";
        break;
    default:
        break;
    }
}

at main loop I create sprite object on the heap after sf::RenderWindow and after window.clean(...) call updateTextureAtLine() function from the for loop.

No error returned and at debug it seems fine too, I am new into it but it was looking like texture is always at memory, I cant find out where is the problem.

Solved as described below.


Solution

  • getTextureByID() returns a copy of a texture and not a reference. This copy is destroyed when it goes out of scope - so as soon as the call to sprite.setTexture() finishes.
    This results in the Sprite having a pointer to a texture that no longer exists.

    The solution is to instead return a pointer or a reference from getTextureByID(). Whilst we are changing that function we could should also make the function and returned value const as we are not planning on modifying it, although this is optional - it is a good habit I might as well point out.

    Here is an example of a const function that returns a const reference:

    // header
    const sf::Texture& getTextureByID(int id) const;
    // source
    const sf::Texture& textures::getTextureByID(int id) const {
        return texturesVector[id];
    }
    

    This should hopefully solve your problem.


    Unrelated notes:

    Your textures class is storing four textures total, two dirt textures, and two grass textures.
    This might be what you intended, or this might not be.
    One solution is to not have the member variables dirt_Texture and grass_Texture or just not to have texturesVector.
    The other solution is to make texturesVector store pointers to textures, these could also be const and this would look like this:

    // header
    std::vector<const sf::Texture*> texturesVector;
    // source
    //   constructor
    texturesVector.push_back(&dirt_Texture);
    ...
    texturesVector.push_back(&grass_Texture);
    //   getTextureByID()
    return *texturesVector[id]; // (or alternatively return a pointer)
    

    Finally, if you ARE storing texture objects inside of texturesVector (if you switch to pointers this won't be a problem), then note that adding any more textures might force the internal array of the vector to change memory location and thus invalidate your textures.
    If you are not planning on adding more textures midway through running the program, then that is ok. I do add textures midway through running programs a lot because I like lazy initialization. If you are planning on adding more textures, then either use another container that does not move it's objects, or dynamically allocate a place for your textures. I am a fan of the latter, using a map of strings to unique pointers of textures (std::unordered_map<std::string, std::unique_ptr<sf::Texture>>)