c++game-developmentsfmlraii

White square instead of an actual image


I started to learn SFML recently through a book and came up with the problem that when you load an image you get a big white square instead.

I have class TextureHolder for managing game resources, such as textures, sprites, and so on (not finished yet ). I tried to give it a RAII approach so now it doesn't work (I learn through book)

Texture.hpp

#pragma once

#include <map>
#include <memory>
#include <SFML/Graphics.hpp>

namespace Textures {
    enum class ID { Landscape, Airplane, Missile };
}

class TextureHolder final {
public:
    void load(Textures::ID id, const std::string& filename);
    sf::Texture& get(Textures::ID id);
    const sf::Texture& get(Textures::ID id) const;

private:
    std::map<Textures::ID, std::unique_ptr<sf::Texture>> m_TextureMap;
};

Texture.cpp

#include <iostream>

#include "Textures.hpp"

void TextureHolder::load(Textures::ID id, const std::string &filename) {
    std::unique_ptr<sf::Texture> texture(new sf::Texture());
    if (!texture->loadFromFile(filename)) {
        std::cerr << "Failed to load texture from file: " << filename << std::endl;
        return;
    }

    m_TextureMap.insert(std::make_pair(id, std::move(texture)));
}

sf::Texture &TextureHolder::get(const Textures::ID id) {
    const auto found = m_TextureMap.find(id);
    return *found->second;
}

const sf::Texture& TextureHolder::get(const Textures::ID id) const {
    const auto found = m_TextureMap.find(id);
    return *found->second;
}

Game.hpp

#pragma once

#include <bitset>
#include <SFML/Graphics.hpp>

enum Movement {
    Up,
    Down,
    Left,
    Right,
    MovementCount
};

class Game final {
public:
    explicit            Game();
    void                run();
    ~Game() =           default;
private:
    void                processEvents();
    void                update(sf::Time deltaTime);
    void                render();
    void                handlePlayerInput(sf::Keyboard::Key , bool );

    sf::RenderWindow  m_window;
    sf::Texture       m_texturePlane;
    sf::Sprite        m_spritePlane;

    std::bitset<MovementCount> m_isMoving;
};

Game.cpp

#include "Game.hpp"

#include <iostream>

#include "Textures.hpp"

namespace {
    constexpr short WIDTH = 640;
    constexpr short HEIGHT = 480;
    const std::string TITLE = "SFML Application";
    constexpr float PLAYER_SPEED = 500.0f;
}

Game::Game() : m_window(sf::VideoMode(WIDTH, HEIGHT), TITLE) {
    m_window.setVerticalSyncEnabled(true);

    TextureHolder texture_holder;
    texture_holder.load(Textures::ID::Airplane, "/home/davit/CLionProjects/untitled/plane-animated-top-down-game-art.png");
    m_spritePlane.setTexture(texture_holder.get(Textures::ID::Airplane));
}

void Game::run() {
    sf::Clock clock;
    sf::Time timeSinceLastUpdate = sf::Time::Zero;
    const auto TimePerFrame = sf::seconds(1.f / 60.f);
    while (m_window.isOpen()) {
        const sf::Time elapsedTime = clock.restart();
        timeSinceLastUpdate += elapsedTime;
        while (timeSinceLastUpdate > TimePerFrame) {
            timeSinceLastUpdate -= TimePerFrame;
            processEvents();
            update(TimePerFrame);
        }
        render();
    }
}

void Game::processEvents() {
    sf::Event event;
    while (m_window.pollEvent(event)) {
        switch (event.type) {
            case sf::Event::KeyPressed:
                handlePlayerInput(event.key.code, true);
                break;
            case sf::Event::KeyReleased:
                handlePlayerInput(event.key.code, false);
                break;
            case (sf::Event::Closed):
                m_window.close();
                break;
        }
    }
}

void Game::handlePlayerInput(sf::Keyboard::Key key, bool isPressed) {
    if (key == sf::Keyboard::W)
        m_isMoving[Up] = isPressed;
    else if (key == sf::Keyboard::S)
        m_isMoving[Down] = isPressed;
    else if (key == sf::Keyboard::A)
        m_isMoving[Left] = isPressed;
    else if (key == sf::Keyboard::D)
        m_isMoving[Right] = isPressed;
}

void Game::update(const sf::Time deltaTime) {
    sf::Vector2f movement(0.f, 0.f);
    if (m_isMoving[Up])
        movement.y -= PLAYER_SPEED;
    if (m_isMoving[Down])
        movement.y += PLAYER_SPEED;
    if (m_isMoving[Left])
        movement.x -= PLAYER_SPEED;
    if (m_isMoving[Right])
        movement.x += PLAYER_SPEED;

    m_spritePlane.move(movement * deltaTime.asSeconds());
}

void Game::render() {
    m_window.clear();
    m_window.draw(m_spritePlane);
    m_window.display();
}

Solution

  • The problem here is that same as the last time you posted this code.

    From the SFML documentation for sf::Sprite::setTexture.

    The texture argument refers to a texture that must exist as long as the sprite uses it.

    This is not true for your code and that's why you get a white rectangle.

    Game::Game() : m_window(sf::VideoMode(WIDTH, HEIGHT), TITLE) {
        m_window.setVerticalSyncEnabled(true);
    
        TextureHolder texture_holder;
        texture_holder.load(Textures::ID::Airplane, "/home/davit/CLionProjects/untitled/plane-animated-top-down-game-art.png");
        m_spritePlane.setTexture(texture_holder.get(Textures::ID::Airplane));
    } // *** all textures are destroyed here ***
    

    The texture_holder variable is destroyed at the end of the Game constructor and all the textures in it are destroyed at the same time. You must move the texture holder out of the Game constructor. Put it in the Game class for instance.

    Like this

    class Game {
        ...
        TextureHolder     m_texture_holder; // <==== HERE
        sf::RenderWindow  m_window;
        sf::Texture       m_texturePlane;
        sf::Sprite        m_spritePlane;
    

    By putting the texture holder in the Game class you ensure that all the textures live as long as the Game object lives.