如何在 SFML C++ 游戏中将资产加载与主循环分开?

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

我正在使用 C++ 中的 SFML 制作游戏。我想将精灵的创建分离到一个单独的函数中,以减少 运行 函数(包含游戏循环的函数)中的混乱。

我制作了一个名为 AssetHolder 的结构,以 std::map<std::string, resource_type> 的形式保存各种资源,如纹理、声音等。考虑下面的代码片段。

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

在我的主函数中,我简单地调用了game::init()game::loop()

但是当我运行这段代码时,它不起作用。该程序不会崩溃。它只是显示一个白色矩形来代替精灵。我猜是因为加载函数结束时,数据被删除了。

那我怎样才能正确地做到这一点?

PS:如果你想知道什么是 Scene 以及我为什么要返回它; Scene 是一个枚举,表示可能 scenes/gamestates。 运行功能在一个场景returns下一个场景。

正如评论中OutOfBound所说,你基本上做了你想做的,只是没有完成。 :)

现在您的资产是 "managed" 一张简单的地图。这很好,但您需要更多逻辑。

一些快速编造的例子:

class AssetHolder {
public:
    const sf::Texture &getTexture(std::string file) {
        auto a = mTextures.find(file);

        if (a != mTextures.end()) // Exists already
            return &a.second; // Just return it

        // Otherwise load the texture and save it for later
        const sf::Texture &tex = mTextures[file]; // Implicit creation

        // Load the texture
        if(!tex.loadFromFile(file)) // Try to load the texture
            throw "OMG the texture didn't load!"; // This needs proper error handling of course

        return text; // Return the texture
    }

private:
    std::map<std::string, sf::Texture> mTextures;
}

您场景的 load() 成员可能如下所示:

void load(AssetHolder &assets) {
    sprites["background"].setTexture(assets.getTexture("assets/images/menuBackgroundTex.png"));
}

出于明显的原因,您也可以选择只传递一个字符串常量。

最后,在创建场景时,调用 load() 成员一次:

menuScene::load(assets);

这将加载您需要的所有资产,同时确保不会加载任何内容两次(即重复使用资源)。

在 game.cpp 文件中,我在堆栈上创建了 sf::Texture 对象。我创建了一个局部变量并使用它的地址。因此,一旦函数结束,由于数据在堆栈上,它也会带走数据。

我尝试使用 new 关键字在堆上分配数据并且成功了。由于数据是在堆上分配的,因此在函数结束后它仍然可以访问。

PS:比使用普通 new 关键字更好的解决方案是将它与 std::unique_ptr 一起使用。与通过 new 关键字获得的指针不同,它不必显式删除。因此,它可以防止意外内存泄漏。