Где я должен хранить текстуры для игры? - PullRequest
0 голосов
/ 10 ноября 2018

В настоящее время я создаю игру на SFML с использованием C ++, и мне интересно: какова хорошая практика для размещения текстур? Должен ли я хранить его с моим проектом? Или мой исполняемый файл? Или даже в чем-то вроде папки Documents? Что будет наиболее эффективным, когда теоретически будет выпущена игра, поскольку она будет включать не просто проект, а скорее его скомпилированную и собранную версию?

1 Ответ

0 голосов
/ 13 ноября 2018

Большинство распространенных выпусков игр имеют свои текстуры внутри папки Media или аналогичной. Внутри этой папки также помещены звуки, музыка и другое содержимое, обычно в отдельные папки.

Они не могут быть частью исполняемого файла (насколько я знаю). Более важно то, как вы управляете этими текстурами внутри вашего кода, это должно быть эффективным способом. Я добавил объяснение о том, как это сделать, если вам интересно.

TL DR

Исходя из собственного опыта создания небольших видеоигр, я обнаружил, что лучше использовать Resource Holder . Это универсальный контейнер для любого тяжелого ресурса (текстуры, музыка, звуки или даже шрифты).

Основная идея заключается в том, чтобы иметь карту, которая связывает ключ (ID) с ресурсом. Поскольку вы можете хранить разные виды ресурсов, лучше создать общий класс.

Базовая реализация:

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;
};

Обычно я предпочитаю разделять .hpp и .cpp, но я объединил их, чтобы избежать (даже) более длинного поста.

Для поддержания чистоты и полезности рекомендуется иметь Идентификатор ресурса заголовочный файл, в котором вы можете объявлять типы для ваших владельцев ресурсов и ваши идентификаторы ресурсов.

// 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;

В качестве примера использования, если у вас есть что-то вроде Game класса (класс, который будет загружаться, пока ваше приложение работает), вы можете сделать так:

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;
};

Ключ с этим подходом - , чтобы ваши держатели были внутри всегда загруженного класса и передавать их как указатели или ссылки. Еще один хороший способ сделать это - иметь класс Context, который содержит и группирует эти указатели только в один класс, и использует этот контекст в качестве параметра (даже путем копирования, потому что это легкий класс) всех ваших классов, которые будут нужен ресурс:

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;
};

Вы можете найти больше информации об этом здесь: Разработка игр SFML , источник этой реализации.

...