【问题标题】:How to separate loading of assets from main loop in SFML C++ game?如何在 SFML C++ 游戏中将资产加载与主循环分开?
【发布时间】:2017-06-23 10:17:03
【问题描述】:

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

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

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

游戏.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 是一个枚举,表示可能的场景/游戏状态。一个场景中的run函数返回下一个场景。

【问题讨论】:

  • 你必须调用你的加载函数,就像你调用你的渲染函数一样。在你的游戏循环之前添加load(assets)
  • @OutOfBound 我在另一个文件中调用它:game.cpp。我会把它添加到问题中。
  • 您将场景命名空间而不是继承类是否有某些特定原因?这样你就可以在构造函数和析构函数中添加资源加载/销毁,并使用一个通用接口来定义它们。
  • @Mario 好点。我没有想到。我使用命名空间的原因是这里有很多答案建议使用命名空间而不是“静态”类。我的场景不需要多个实例。因此,使用静态成员或使用命名空间创建类是唯一的选择。我认为这一定是一种好的做法。非常感谢您的帮助! :)
  • 仅仅因为你不需要一个类的多个实例并不意味着所有的方法都应该是静态的。事实上,这很可能是一个迹象,它不应该是。定义一个只包含成员并且只有实例的类是非常常见的,因为类的资源可以由构造函数和析构函数很好地处理。如果你想在你的程序中添加另一个实例,你可以这样做,而不需要对你的代码库进行任何重大更改。

标签: c++ sfml code-organization


【解决方案1】:

正如 OutOfBound 在 cmets 中提到的,你基本上做了你想做的,只是没有完成。 :)

现在您的资产由一个简单的地图“管理”。这很好,但您需要更多的逻辑。

一些简单的例子:

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

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

【讨论】:

  • 我觉得你的回答真的很有帮助。你能告诉我为什么我的代码不起作用吗?或者也许只是告诉我应该关注的领域(如提示)。我不想再遇到类似的问题。再次感谢你! :)
  • @NightMare 并没有真正尝试跟踪它,但白色纹理通常意味着资源从未加载或被破坏,例如通过意外使用副本。最好的办法是逐步调试程序。
  • 我会试试的。再次感谢。 :)
  • 我发现了我的错误并写了一个单独的答案。尽管如此,我还是从这个例子中学到了。谢谢你。 :)
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-14
    • 1970-01-01
    • 2022-01-25
    • 2014-03-06
    相关资源
    最近更新 更多