SFML Game Development

Author: Artur Moreira
3.6
This Month Stack Overflow 1

Comments

by anonymous   2019-01-13

Most common game releases have their textures inside a Media folder or similar. Inside that folder are also placed sounds, music, and other content, tipically into separate folders.

They can't be part of the executable (as far as I know). More important is how do you manage those textures inside your code, it should be an efficient way. I've added an explanation about how to do that, if you're interested.

TL DR

From my own experience making some small videogames, I found better to use a Resource Holder. This is a generic container for any heavy resource (textures, music, sounds or even fonts).

The main idea behind this is to have a map which relates a key (an ID) with a resource. As you may want to store diferent kinds of resources, it's better to make a generic class.

A basic implementation:

template <typename Resource, typename Identifier>
class ResourceHolder
{
public:
    void load(Identifier id, const std::string& filename){
        // Create and load resource
        std::unique_ptr<Resource> resource(new Resource());
        if (!resource->loadFromFile(filename))
            throw std::runtime_error("ResourceHolder::load - Failed to load " + filename);

        // If loading successful, insert resource to map
        insertResource(id, std::move(resource));
    }

    Resource& get(Identifier id){
        auto found = mResourceMap.find(id);
        assert(found != mResourceMap.end());

        return *found->second;
    }

    const Resource& get(Identifier id) const {
        auto found = mResourceMap.find(id);
        assert(found != mResourceMap.end());

        return *found->second;
    }


protected:
    void insertResource(Identifier id, std::unique_ptr<Resource> resource){
        // Insert and check success
        auto inserted = mResourceMap.insert(std::make_pair(id, std::move(resource)));
        assert(inserted.second);
    }


protected:
    std::map<Identifier, std::unique_ptr<Resource>> mResourceMap;
};

I normally prefer to keep separate .hpp and .cpp, but I merged them to avoid a (even) longer post.

To keep things clean and useful, it's a good practice to have Resource Identifier header file, where you can declare types for your resource holders, and your resource identifiers too.

// Forward declaration of SFML classes
namespace sf
{
    class Texture;
    // If you need, you can use other SFML classes into your holders the same way
    //class Font;
    //class SoundBuffer;
}

namespace Textures
{
    enum ID
    {
        TitleScreen,
        LoadingScreen,
        GameOverScreen,
        Title,
        Controls,
        GUI,
        TileMap,
        Player,
        Enemy,
        Key,
        PlayerMods
    };
}

// Forward declaration and a few type definitions
template <typename Resource, typename Identifier>
class ResourceHolder;

typedef ResourceHolder<sf::Texture, Textures::ID>   TextureHolder;
//typedef ResourceHolder<sf::Font, Fonts::ID>           FontHolder;
//typedef ResourceHolder<sf::SoundBuffer, Sounds::ID>   SoundHolder;

As an example of use, if you have something like a Game class (a class that will be loaded as long as your application is running), you can do like this:

class Game {
public:
    Game() :
        _window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Game")
    {
        // EXAMPLES
        //_fonts.load(Fonts::Main, FONTS_FOLDER + "font.ttf");
        //_musics.load(Musics::Game, MUSIC_FOLDER + "main.ogg");
        //_musics.get(Musics::Game).setLoop(true);
        //_sounds.load(Sounds::Key, SOUNDS_FOLDER + "key.wav");


        _textures.load(Textures::TitleScreen, TEXTURES_FOLDER + "titlescreen.png");

        // More code ...
    }

    void run(){
        // Your game loop: process inputs, update and render until you close
    }
private:
    void update(sf::Time dt){
        // ...
    }

    void processInput(){
        // ...
    }

    void render(){
        _window.clear(sf::Color::Black);

        // Here you can use your resources to draw
        sf::Sprite sp(_textures.get(Textures::TitleScreen));
        _window.draw(sp);

        _window.display();
    }

    sf::RenderWindow _window;
    TextureHolder _textures;
    //FontHolder _fonts;
    //SoundHolder _sounds;
};

The key with this approach is to have your holders inside an always loaded class, and pass your holders as pointers or references. Another good way to do that is to have a Context class, which holds and group those pointers into only one class, and use that context as a parameter (even by copy, because it's a light class) of all your classes that will need a resource:

struct Context
{
    Context(sf::RenderWindow& window, TextureHolder& textures, FontHolder& fonts, MusicHolder& musics, SoundHolder& sounds);

    sf::RenderWindow*   window;
    TextureHolder*      textures;
    FontHolder*         fonts;
    MusicHolder*        musics;
    SoundHolder*        sounds;
};

You can find more info about this here: SFML Game Development, source of this implementation.