You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SFML自定义瓦片地图显示白色方块而非精灵的问题求助

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 引用的纹理就会失效,最终绘制出白色方块。

看你代码里的循环逻辑:

  1. 创建局部的 Tile tile 对象,加载纹理到 tile.texture
  2. 给 tile.sprite 设置纹理,绑定的是局部 tile 的 texture
  3. 用 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() 让向量容量等于元素数量,避免后续扩容导致元素移动,保证纹理地址始终不变

额外检查建议

  1. 确保纹理路径确实正确:可以在加载纹理时打印 tile.file 的值,检查是否和实际文件路径一致
  2. 确认图片格式是SFML支持的(PNG、JPG、BMP等),且图片没有损坏
  3. 如果你后续需要动态修改 tiles 向量,建议改用 std::list<Tile> 存储(元素不会被移动),或者将 sf::Texture 存储在独立的 std::vector<std::unique_ptr<sf::Texture>> 中,让 Sprite 绑定这些智能指针指向的纹理

这样修改后,你的瓦片地图应该就能正常显示纹理了 😊

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:48:11