Search code examples
c++inheritancesfml

SFML draw call to subclass draws as white square


I'm working on a game project and found myself getting stuck on the following issue:

I have a class called Game : public sf::Drawable which I use to (among other things), draw everything in my game. Game contains a class called Player : public Entity, which in turn is a subclass of Entity : public sf::Drawable.


These classes are slightly simplified, but the affected functions are the same:

Entity

class Entity : public sf::Drawable
{
    private:
        sf::Sprite eSprite;
        sf::Texture eTex;
        std::string texpath;
    public:
        virtual Entity(std::string texpath, sf::IntRect intrect){
            this->texpath = texpath;
            eTex.loadFromFile(texpath, intrect);
            eSprite.setTexture(eTex); }
        virtual ~Entity(){}
        virtual void draw(sf::RenderTarget target, sf::RenderStates states)const{
                target.draw(this->eSprite);}
    //Lots of other functions
}

Player

class Player : public Entity¨
    {
    public:
        ~Player(){}
        Player(std::string texpath, sf::IntRect spriteintrect)
            :Entity(texpath, spriteintrect){}
        void draw(sf::RenderTarget target, sf::RenderStates states){
            Entity::draw(target, states); }
    }

Game

#define PLAYER_START "../filepath/image.png", 
                          sf::IntRect{0,0,40,60,}, sf::Vector2f(320.0f, 200.0f) //Ease of access

   class Game : public sf::Drawable
{
private:
    Player player;
public:
    Game() { player = Player(PLAYER_START); };
    ~Game() {};
    void draw(sf::RenderTarget &target, sf::RenderStates states)const { target.draw(player); }

};

For the sake of making the problem easy to understand, I created the following code example:

int main(){
    sf::RenderWindow window(sf::VideoMode(640, 480), "Game Test");
    sf::Event event = sf::Event{};
    Game game;

    while (window.isOpen())
    {
        while (window.pollEvent(event))
            if (event.type == sf::Event::Closed) 
                window.close();
        window.clear();
        window.draw(game); 
        window.display();
    }
}

This code produces a white square.

I have tried:

  • Creating a Player outside of Game and calling window.draw(player); on it. This works.
  • Creating a new Player through assignment operator and drawing. This works.
  • Creating a new Player through the copy-constructor and drawing. This works.
  • Inserting Player into a Game class and drawing that. This does not work and is what I illustrate with the code above

I realise this problem could (probably) be solved by moving the sf::Sprite and sf::Texture to the Player class, but since I want to eventually derive Coin and Enemy from the Entity base class, I'd prefer to solve the problem as-is.*

Thanks for any help /Lore


Solution

  • Objects of sf::Sprite store POINTER to textures.

    When you have player = Player() for base class will be called copy assignment operator - generated by compiler by default. It copies all member variables one by one.

    After executing that copy assignment for:

    entity1 = entity2;
    

    we have:

    entity1.eSprite = entity2.eSprite; // shallow copy
    entity1.eTex = entity2.eTex;
    

    and the key point - what texture is pointed by entity1.eSprite ? It is entity2.eTex! And unfortunately for you, entity2 is temporary in your case, so you have dangling pointer to texture inside sprite ... common mistake in SFML and it is described in offical reference of SFML.

    You successfully loaded a texture, constructed a sprite correctly, and... all you see on your screen now is a white square. What happened?

    This is a common mistake. When you set the texture of a sprite, all it does internally is store a pointer to the texture instance. Therefore, if the texture is destroyed or moves elsewhere in memory, the sprite ends up with an invalid texture pointer.

    Solution, add copy assignment operator which properly sets texture for sprite, as deep-copy:

    Entity& operator=(const Entity& theOther)  {
        if (this != &theOther) {
            this->eTex = theOther.eTex;
            this->eSprite.setTexture(this->eTex); // the most imporant line in this answer
        }
        return *this;
    }