Search code examples
c++graphicstexturesspritesfml

SFML - Sprite is blank after loading from texture


I am using SFML to display a sprite on the screen. I first use a texture and call loadFromFile() to load the image. This works well with no errors occurring. It seems that the image path is correct and it loads the image, but it doesn't seem to store the data in the sprite. The sprite is displayed without error, but it shows a blank white image.

Is there a problem with my code in the following snippets? They should be fairly easy to read :).

My code:

Main.cpp

#include <SFML/Graphics.hpp>
#include "Menu.h"

using namespace sf;

int main()
{
    VideoMode vm = VideoMode::getDesktopMode();
    int width = vm.width, height = vm.height;
    RenderWindow window(VideoMode(width, height), "Score Arena v1.0", Style::Fullscreen);
    window.setPosition(Vector2i(0, 0));

    Menu menu(width, height);

    while (window.isOpen())
    {
        Event event;
        while (window.pollEvent(event))
        {
            switch (event.type) {
                case Event::Closed:
                    window.close();
                    break;

                //when a key is pressed
                case Event::KeyPressed:
                    if (Keyboard::isKeyPressed(Keyboard::Escape))
                        window.close();
                    break;
            }
        }

        window.clear();
        
        menu.draw(window);

        window.display();
    }

    return 0;
}

Menu.h

#pragma once
#include <string>
#include <SFML/Graphics.hpp>

using namespace std;
using namespace sf;

class Menu {
        int page = 0;
        string title = "Score Arena";
        string labels[4]{
                "Single Player",
                "Multi Player",
                "Options",
                "Exit"
        };
        Sprite background;

        int width, height;

public:
        Menu(int, int);
        void draw(RenderWindow &window);
};

Menu.cpp

#include "Menu.h"
#include <SFML/Graphics.hpp>
#include <iostream>

Menu::Menu(int width, int height) {
        this->width = width;
        this->height = height;

        Texture bg;
        if (!bg.loadFromFile("menuBg.jpg"))
                std::cout << "Error loading image!" << std::endl;
        background.setTexture(bg);
}

void Menu::draw(RenderWindow &window) {
        window.draw(background, RenderStates::Default);
}

Solution

  • An sf::Sprite object doesn't internally copy the passed sf::Texture object when you call the setTexture() member function. An sf::Sprite just refers to an sf::Texture; the former doesn't own the latter. The associated sf::Texture must exist when you render the sf::Sprite.

    It is stated in the documentation for sf::Sprite:

    It is important to note that the sf::Sprite instance doesn't copy the texture that it uses, it only keeps a reference to it. Thus, a sf::Texture must not be destroyed while it is used by a sf::Sprite (i.e. never write a function that uses a local sf::Texture instance for creating a sprite).

    The problem is in your Menu's constructor:

    Menu::Menu(int width, int height) {
            this->width = width;
            this->height = height;
    
            Texture bg; // <-- it is local variable !!!
            if (!bg.loadFromFile("menuBg.jpg"))
                    std::cout << "Error loading image!" << std::endl;
            background.setTexture(bg); // <-- associating sprite to a local object!!!
            // bg goes out of existence!!!
    }
    

    The sf::Texture object, bg, ceases to exist as soon the program control flow leaves the constructor because bg is a local variable inside the constructor. As a result, the sf::Sprite object, background – a Menu data member – outlives its associated sf::Texture object, bg – a local object.

    Then, in Menu::draw() you use this sf::Sprite object, background, which internally refers to an sf::Texture object that no longer exists:

    void Menu::draw(RenderWindow &window) {
            window.draw(background, RenderStates::Default);
    }
    

    Possible Solution

    Instead of the sf::Texture object being a local object in Menu's constructor, you could make it a data member of Menu:

    class Menu {
            // ...
            Texture bg;
            Sprite background;
            // ...
    };
    

    This way, Menu owns bg (in contrast to Menu's constructor owning bg as was the case before), and therefore Menu controls bg's lifetime: bg is kept alive while the Menu object lives. This time, when you call Menu::draw(), the sf::Sprite object does refer to a living sf::Texture object.