SFML自定义瓦片地图显示白色方块而非精灵的问题求助
我正在用C++的SFML库从零开始做一个游戏,目前在实现自定义瓦片地图系统。这个系统逻辑很简单:读取一个特定结构的JSON文件,提取坐标和纹理路径,创建对应的Sprite并放置到指定位置。我之后打算做一个GUI工具来生成这些JSON,但现在先不管这个。
目前系统“理论上”应该能工作,但实际运行时,Sprite没有显示出来,只看到白色方块。坐标是对的(位置没问题),我也检查过纹理路径,应该没问题。这是我第一次用SFML,实在找不到问题出在哪,以下是我的代码:
tilemap.cpp
#include "header/tilemap.hpp" #include <memory> void splitCoord(const std::string& coord, float& posX, float& posY) { size_t virgola = coord.find(','); assert(virgola != std::string::npos && "Coordinata non valida, manca la virgola"); posX = std::stof(coord.substr(0, virgola)); posY = std::stof(coord.substr(virgola + 1)); } TileMap::TileMap() : fileStream("assets/tilemap/tiles.json") { assert(fileStream.is_open() && "Impossibile caricare i dati della tilemap"); datiJson = nlohmann::json::parse(fileStream); tiles.clear(); for (const auto& tileJson : datiJson) { assert(tileJson.contains("file") && "Impossibile trovare il campo 'file' nel json"); assert(tileJson.contains("coord") && "Impossibile trovare il campo 'coord' nel file json"); Tile tile; tile.file = tileJson["file"].get<std::string>(); tile.coord = tileJson["coord"].get<std::string>(); splitCoord(tile.coord, tile.posX, tile.posY); if (!tile.texture.loadFromFile(tile.file)) { assert(false && "Impossibile caricare la texture del tile"); } tile.sprite = std::make_unique<sf::Sprite>(); tile.sprite->setTexture(tile.texture); tile.sprite->setPosition({tile.posX, tile.posY }); tiles.push_back(std::move(tile)); } }
tilemap.hpp
#pragma once #include <nlohmann/json.hpp> #include <fstream> #include <SFML/System/FileInputStream.hpp> #include <SFML/Graphics.hpp> #include <string> #include <sstream> #include <cassert> struct Tile { std::string file; std::string coord; sf::Texture texture; std::unique_ptr<sf::Sprite> sprite; float posX = 0; float posY = 0; }; class TileMap { private: std::ifstream fileStream; nlohmann::json datiJson; public: std::vector<Tile> tiles; TileMap(); };
game.cpp(调用瓦片地图的代码)
#include <SFML/Graphics.hpp> #include "header/assets.hpp" #include "header/utils.hpp" #include "header/tilemap.hpp" void avviaGioco(sf::RenderWindow& finestra) { finestra.setSize(sf::Vector2u(1280, 720)); Utils::centraFinestra(finestra, sf::VideoMode({1280, 720})); TileMap tilemap; while (finestra.isOpen()) { while (const std::optional event = finestra.pollEvent()) { if (event->is<sf::Event::Closed>()) { finestra.close(); } } finestra.clear(); for (const auto& tile : tilemap.tiles) { if (tile.sprite) finestra.draw(*tile.sprite); } finestra.display(); } }
示例tiles.json
[ { "file": "assets/tilemap/tile_00.png", "coord": "100,100" } ]
问题原因分析
这是SFML开发中非常常见的新手坑:sf::Sprite 保存的是对 sf::Texture 的引用(内存地址),如果 Texture 被移动或销毁,Sprite 引用的纹理就会失效,最终绘制出白色方块。
看你代码里的循环逻辑:
- 创建局部的
Tile tile对象,加载纹理到tile.texture - 给
tile.sprite设置纹理,绑定的是局部tile的texture - 用
std::move(tile)将局部对象转移到tiles向量中
当你移动 Tile 对象时,sf::Texture 的移动构造函数会把纹理资源从局部 tile 转移到向量中的 tile 里,原来的局部 tile.texture 会被置空。而你的 sprite 绑定的是局部 tile.texture 的内存地址,现在这个地址的 Texture 已经没有任何资源了,所以绘制时就会显示白色方块。
另外,如果后续 tiles 向量因为扩容而重新分配内存,向量中的 Tile 会被再次移动,同样会导致 Sprite 绑定的纹理引用失效。
解决方案
我们需要让 Sprite 直接绑定最终存活位置的 Texture,同时避免向量扩容导致的元素移动。具体修改如下:
1. 修改 TileMap 构造函数的循环部分
直接在 tiles 向量中构造 Tile 对象,而不是先创建局部对象再移动:
TileMap::TileMap() : fileStream("assets/tilemap/tiles.json") { assert(fileStream.is_open() && "Impossibile caricare i dati della tilemap"); datiJson = nlohmann::json::parse(fileStream); tiles.clear(); for (const auto& tileJson : datiJson) { assert(tileJson.contains("file") && "Impossibile trovare il campo 'file' nel json"); assert(tileJson.contains("coord") && "Impossibile trovare il campo 'coord' nel file json"); // 直接在向量末尾构造Tile对象,避免局部对象移动 tiles.emplace_back(); Tile& tile = tiles.back(); tile.file = tileJson["file"].get<std::string>(); tile.coord = tileJson["coord"].get<std::string>(); splitCoord(tile.coord, tile.posX, tile.posY); if (!tile.texture.loadFromFile(tile.file)) { assert(false && "Impossibile caricare la texture del tile"); } // 这里绑定的是向量中tile的texture,生命周期和向量一致 tile.sprite = std::make_unique<sf::Sprite>(tile.texture); tile.sprite->setPosition({tile.posX, tile.posY }); } // 收缩向量容量,避免后续扩容导致元素移动 tiles.shrink_to_fit(); }
2. 关键修改点说明
- 用
tiles.emplace_back()直接在向量中构造Tile,跳过局部对象的创建和移动步骤,纹理直接加载到向量的Tile对象中 Sprite绑定的是向量中Tile的texture,其生命周期和TileMap一致,不会失效- 最后调用
tiles.shrink_to_fit()让向量容量等于元素数量,避免后续扩容导致元素移动,保证纹理地址始终不变
额外检查建议
- 确保纹理路径确实正确:可以在加载纹理时打印
tile.file的值,检查是否和实际文件路径一致 - 确认图片格式是SFML支持的(PNG、JPG、BMP等),且图片没有损坏
- 如果你后续需要动态修改
tiles向量,建议改用std::list<Tile>存储(元素不会被移动),或者将sf::Texture存储在独立的std::vector<std::unique_ptr<sf::Texture>>中,让Sprite绑定这些智能指针指向的纹理
这样修改后,你的瓦片地图应该就能正常显示纹理了 😊
内容来源于stack exchange

