Search code examples
c++sfmlcode-organization

How to separate loading of assets from main loop in SFML C++ game?


I'm making a game using SFML in C++. I wanted to separate creation of sprites into a separate function to reduce cluttering in the run function(the function containing the game loop).

I've made a struct called AssetHolder to hold various resources like textures, sounds etc in the form of std::map<std::string, resource_type>. Consider the code snippets below.

assetHolder.h

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

struct AssetHolder {
    std::map<std::string, sf::Texture*> textures;
    //Other resources I may add in future.
};

menuScene.cpp:

#include"menuScene.h"

namespace menuScene {

    std::map<std::string, sf::Sprite> sprites;

    void load(AssetHolder &assets) {
        sprites["background"].setTexture(*assets.textures["menuBackgroundTex"]);
    }

    void render(sf::RenderWindow &window) {
        window.clear(sf::Color::Magenta);
        window.draw(sprites["background"]);
        window.display();
    }

    Scene run(sf::RenderWindow &window) {
        while(true) {
            sf::Event event;
            while(window.pollEvent(event)) {
                if(event.type == sf::Event::Closed) {
                    return Scene::Null;
                }
            }
            render(window);
        }
    }
}

game.cpp

#include"game.h"

namespace game {

    sf::RenderWindow window;
    AssetHolder assets;
    Scene currentScene = Scene::Menu;

    void init() {
        window.create(sf::VideoMode(640, 360), "Platformer");
        window.setFramerateLimit(60);

        sf::Texture menuBackgroundTex;
        menuBackgroundTex.loadFromFile("assets/images/menuBackgroundTex.png");
        menuBackgroundTex.setRepeated(true);
        assets.textures["menuBackgroundTex"] = &menuBackgroundTex;

        menuScene::load(assets);
    }

    void loop() {
        while(window.isOpen()) {
            switch(currentScene) {
                case Scene::Menu: {
                    currentScene = menuScene::run(window);
                    break;
                }
                case Scene::Play: {
                    currentScene = playScene::run(window);
                    break;
                }
                case Scene::Exit: {
                    currentScene = exitScene::run(window);
                    break;
                }
                case Scene::Null: {
                    window.close();
                    break;
                }
            }
        }
    }
}

In my main function, I simply call game::init() and game::loop().

But when I run this code, it doesn't work. The program doesn't crash. It just displays a white rectangle in place of the sprite. I guess it is due to the fact that when the load function is over, the data is deleted.

How can I do this correctly then?

PS: If you wonder what is Scene and why I am returning it; Scene is an enum denoting the possible scenes/gamestates. The run function in a scene returns the next scene.


Solution

  • In the game.cpp file I was creating the sf::Texture object on the stack. I created a local variable and was using it's address. So, as soon as the function got over, it took away the data too due to it being on the stack.

    I tried using the new keyword to allocate data on the heap & it worked. As the data was allocated on the heap, it remained accessible after the function got over.

    PS: A better solution than using the plain new keyword is using it with std::unique_ptr. Unlike a pointer obtained via the new keyword, it doesn't have to be explicitly deleted. So, it prevents accidental memory leaks.