如何在 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 关键字获得的指针不同,它不必显式删除。因此,它可以防止意外内存泄漏。
我正在使用 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 关键字获得的指针不同,它不必显式删除。因此,它可以防止意外内存泄漏。