I'm creating a tile based game in SFML with C++ and while I was rendering the map of tiles with the camera moving I get a bug where sometimes while moving the camera there are lines that appear between some of the tiles then they disappear for a while.
This was not happening until I tried to set the position of the mapSprite to the cameras position so I could create the mapTexture to the size of the camera instead of the size of the map in case it is big.
I have attached a video of what I mean. Any help would be appreciated and if you have any way I could improve my code that would be great.
#include "Map/Map.hpp"
#include "Utility/GameUtility.hpp"
#include "Utility/PathUtility.hpp"
#include "Utility/XMLUtility.hpp"
#include <SFML/System.hpp>
#include <tinyxml2.h>
#include <cmath>
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <variant>
Map::Map(const std::string & mapFile, const sf::Vector2f & tileScale) : tileScale(tileScale) { LoadFromFile(mapFile); size = layers[0].GetSize(); }
Map::~Map() {}
void Map::Draw(Window & window, Camera & camera, bool debugMode) { // TODO: rework this whole algorithm
sf::FloatRect cameraBounds(camera.GetBounds());
//sf::IntRect cameraTileBounds(GameUtility::PositionToCoordinate(sf::Vector2f(cameraBounds.left, cameraBounds.top), tileSize, tileScale), GameUtility::PositionToCoordinate(sf::Vector2f(cameraBounds.width, cameraBounds.height), tileSize, tileScale) + sf::Vector2i(1, 1));
mapTexture.create(cameraBounds.width, cameraBounds.height);
// mapTexture.create(layers[0].GetSize().x * tileSize.x * tileScale.x, layers[0].GetSize().y * tileSize.y * tileScale.y);
mapTexture.clear();
sf::Vector2f offset(-cameraBounds.left, -cameraBounds.top);
for (Layer & layer : layers) {
if (!debugMode && layer.GetBoolPropertyValue("DontDraw", false)) { continue; }
std::unordered_map<const sf::Texture *, sf::VertexArray> vertexArrays; // TODO: use something other than a texture key
for (Tileset & tileset : tilesets) {
sf::VertexArray newVertexArray(sf::Quads);
vertexArrays.insert(std::make_pair(&tileset.GetTexture(), newVertexArray));
}
sf::Vector2i topLeftCoordinate = GameUtility::PositionToCoordinate(cameraBounds.getPosition(), tileSize, tileScale);
sf::Vector2i bottomRightCoordinate = GameUtility::PositionToCoordinate(cameraBounds.getPosition() + cameraBounds.getSize(), tileSize, tileScale);
for (int y = std::max(topLeftCoordinate.y, 0); y <= std::min(bottomRightCoordinate.y, int(layer.GetSize().y) - 1); y++) {
for (int x = std::max(topLeftCoordinate.x, 0); x <= std::min(bottomRightCoordinate.x, int(layer.GetSize().x) - 1); x++) {
Tile & tile = layer.GetTile(sf::Vector2u(x, y));
if (tile.GetId() <= 0) { continue; }
sf::Vector2u drawnTileSize(tileSize.x * tileScale.x, tileSize.y * tileScale.y);
sf::Vector2f position(x * drawnTileSize.x, y * drawnTileSize.y);
position += offset;
sf::Vector2f texturePosition(tile.GetSprite().getTextureRect().left, tile.GetSprite().getTextureRect().top);
sf::VertexArray & vertexArray = vertexArrays[tile.GetSprite().getTexture()];
sf::Color tileColour(255, 255, 255, 255 * layer.GetOpacity());
vertexArray.append(sf::Vertex(position, tileColour, texturePosition));
vertexArray.append(sf::Vertex(position + sf::Vector2f(drawnTileSize.x, 0), tileColour, texturePosition + sf::Vector2f(tileSize.x, 0)));
vertexArray.append(sf::Vertex(position + sf::Vector2f(drawnTileSize.x, drawnTileSize.y), tileColour, texturePosition + sf::Vector2f(tileSize)));
vertexArray.append(sf::Vertex(position + sf::Vector2f(0, drawnTileSize.y), tileColour, texturePosition + sf::Vector2f(0, tileSize.y)));
}
}
for (const auto & [texture, vertexArray] : vertexArrays) {
mapTexture.draw(vertexArray, sf::RenderStates(texture));
}
}
mapTexture.display();
mapSprite.setTexture(mapTexture.getTexture());
mapSprite.setPosition(cameraBounds.getPosition());
window.Draw(mapSprite);
}
std::vector<Layer> & Map::GetLayers() { return layers; }
Layer & Map::GetLayerByName(std::string name) {
for (Layer & layer : layers) {
if (layer.GetName() == name) { return layer; }
}
return layers[0];
}
std::vector<Tileset> & Map::GetTilesets() { return tilesets; }
std::string & Map::GetName() { return name; }
sf::Vector2u & Map::GetSize() { return size;}
sf::Vector2u & Map::GetTileSize() { return tileSize; }
sf::Vector2f & Map::GetTileScale() { return tileScale; }
void Map::LoadFromFile(const std::string & mapFile) { // TODO: split into more functions
tinyxml2::XMLDocument mapXML;
mapXML.LoadFile(mapFile.c_str());
tinyxml2::XMLElement * mapElement = mapXML.FirstChildElement("map");
// tileSize = sf::Vector2u(mapElement->IntAttribute("tilewidth"), mapElement->IntAttribute("tileheight"));
tileSize = sf::Vector2u(16, 16);
LoadTilesets(mapElement, mapFile);
LoadLayers(mapElement);
std::cout << "Map Loaded" << std::endl;
}
void Map::LoadTilesets(tinyxml2::XMLElement * mapElement, const std::string & mapFile) {
std::vector<tinyxml2::XMLElement *> tilesetElements = XMLUtility::GetAllElements("tileset", mapElement);
for (tinyxml2::XMLElement * tilesetElement : tilesetElements) {
tilesets.push_back(XMLToTileset(tilesetElement, PathUtility::ExtractDirectory(mapFile)));
}
}
void Map::LoadLayers(tinyxml2::XMLElement * mapElement) {
std::vector<tinyxml2::XMLElement *> layerElements = XMLUtility::GetAllElements("layer", mapElement);
for (tinyxml2::XMLElement * layerElement : layerElements) {
Layer newLayer = XMLToLayer(layerElement);
tinyxml2::XMLElement * propertiesElement = layerElement->FirstChildElement("properties");
if (propertiesElement) {
std::vector<tinyxml2::XMLElement *> propertyElements = XMLUtility::GetAllElements("property", propertiesElement);
for (tinyxml2::XMLElement * propertyElement : propertyElements) {
newLayer.AddProperty(XMLToProperty(propertyElement));
}
}
layers.push_back(newLayer);
}
}
sf::Vector2u Map::PositionToCoordinate(const sf::Vector2f & position) {
return sf::Vector2u(floor((position.x / tileSize.x) / tileScale.x), floor((position.y / tileSize.y) / tileScale.y));
}
sf::Vector2f Map::CoordinatesToPosition(const sf::Vector2u & coordinate) {
return sf::Vector2f((coordinate.x * tileSize.x) * tileScale.x, (coordinate.y * tileSize.y) * tileScale.y);
}
bool Map::IsTileCollidable(sf::Vector2u coordinates) {
return this->GetLayerByName("Collision").GetTile(coordinates).GetId() != 0;
}
Tileset Map::XMLToTileset(tinyxml2::XMLElement * tilesetXML, const std::string & mapDirectory) {
int firstTileId = tilesetXML->IntAttribute("firstgid");
std::string textureFile;
sf::Vector2u tileSize;
std::string tilesetDocumentFile = tilesetXML->Attribute("source");
tinyxml2::XMLDocument tilesetDocument;
tilesetDocument.LoadFile((mapDirectory + tilesetDocumentFile).c_str());
tinyxml2::XMLElement * tilesetElement = tilesetDocument.FirstChildElement("tileset");
tinyxml2::XMLElement * imageElement = tilesetElement->FirstChildElement("image");
textureFile = mapDirectory + PathUtility::ExtractDirectory(tilesetDocumentFile) + imageElement->Attribute("source");
tileSize = sf::Vector2u(tilesetElement->IntAttribute("tilewidth"), tilesetElement->IntAttribute("tileheight"));
return Tileset(firstTileId, textureFile, tileSize);
}
Layer Map::XMLToLayer(tinyxml2::XMLElement * layerXML) {
int id = layerXML->IntAttribute("id");
std::string name = layerXML->Attribute("name");
sf::Vector2u size(layerXML->IntAttribute("width"), layerXML->IntAttribute("height"));
float opacity = layerXML->FloatAttribute("opacity", 1);
// TODO: convert this to just a constructor
Layer newLayer(name, id, size, *this, opacity);
newLayer.LoadTilesFromXML(layerXML->FirstChildElement("data"));
return newLayer;
}
Property Map::XMLToProperty(tinyxml2::XMLElement * propertyXML) {
std::string name = propertyXML->Attribute("name");
std::string type = propertyXML->Attribute("type");
std::string value = propertyXML->Attribute("value");
if (type == "bool") {
return Property(name, value == "true");
}
else if(type == "int") {
return Property(name, std::stoi(value));
}
else if(type == "float") {
return Property(name, std::stof(value));
}
else {
return Property(name, value);
}
}