Really great work thank you!
I just wanted to say, I extended it a little bit to support multiple tilesets, and properties for layers, tiles and objects.
NOTE: this could probably be done a bit more "pro", as I used this to teach myself c++. I could not quite get templates working, so get properties methods are pretty much copied, for each object.
its still in a long thread and still only supports XML. (I like to be able to manually read the maps anyway)
--I removed some stuff that's useless TO ME,
and I am about to change it to just render a single background, (no tiles), (I KNOW??) so it will be faster for me. So I have not finished playing with it, posting as before I edit it for me, (which would make it useless for others).
I don't plan on using objects for collision, but might.
I think in tild you can do object layers? I don't have support for that? just same as above, objects...
be glad to see further feedback.
no idea about uploading, so ill just post code, sorry.
Compare with above for feature sets.
I mainly just use tiles to get info, ...ie......map.getproperyat(layer, x, y). ...I can then find certain things on maps...
getobjectat(x,y)
also reworked the objects , as not all objects have width, height.
apart from other optimizations..things to do.
somehow work out how to link or make a list of the objects which point to their objects in the multi
- [y] layer. So easier to loop through...
/*********************************************************************
Quinn Schwab
16/08/2010
SFML Tiled Map Loader
The zlib license has been used to make this software fully compatible
with SFML. See http://www.sfml-dev.org/license.php
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#ifndef MAP_H
#define Map_H
#include <string>
#include <vector>
#include <map>
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
//Object class. Everything is kept public in case you want to get a different type to the supported ones.
//Also you will be able to modify things. For example doors/changing walls etc (Just give the door a static type and a unique name)
//Kept in same files as Map just for convenience. (only have to add two files to your project)
class Object{
public:
int GetPropertyInt(std::string name);
float GetPropertyFloat(std::string name);
std::string GetPropertyString(std::string name);
std::string name;
std::string type;
sf::Rect <int> rect;
//All properties of the object. Values are stored as strings and mapped by strings(names provided in editor).
std::map <std::string, std::string> properties;
};
class TileSet{
public:
int GetPropertyInt(std::string name);
float GetPropertyFloat(std::string name);
std::string GetPropertyString(std::string name);
int rows;
int columns;
int firstTileID;
sf::String name;
std::string ImgSource;
//The tileset image.
sf::Image TilesetImage;
int width;
int height;
int tileHeight;
int tileWidth;
int spacing;
int margin;
std::map <std::string, std::string> properties;//All properties of the object. Values are stored as strings and mapped by strings(names provided in editor).
};
class Tile{
public:
int GetPropertyInt(std::string name);
float GetPropertyFloat(std::string name);
std::string GetPropertyString(std::string name);
bool drawable;
//the id in the tileset
int tilesettileid;
sf::Vector2i position;
sf::Vector2f coords;
sf::Sprite sprite;
int id;
TileSet *tileset;
//All properties of the object. Values are stored as strings and mapped by strings(names provided in editor).
std::map <std::string, std::string> properties;
};
class Layer{
public:
int GetPropertyInt(std::string name);
float GetPropertyFloat(std::string name);
std::string GetPropertyString(std::string name);
int rows;
int columns;
std::map <int , std::map <int , Tile> > XYTiles;
std::map <int , Tile > YTiles;
std::vector <Tile> tiles;
int visible;
int opacity;
std::map <std::string, std::string> properties;//All properties of the object. Values are stored as strings and mapped by strings(names provided in editor).
};
typedef std::map <int, TileSet> Tilesets;
class Map
{
public:
Map();
virtual ~Map();
//Loads the map. Returns false if it fails.
bool LoadFromFile(std::string filename);
//Returns true if the given tile is solid.
bool IsSolidTile(int x, int y);
//Set the area to draw. This rect is usually provided directly from the view you are using.
void SetDrawingBounds(sf::Rect<float> bounds);
//Draws the map to the provided window.
void Draw(sf::RenderWindow &window);
//Width(tiles), height(tiles), tile width(pixels), tile height(pixels) of the map.
int width, height, tileWidth, tileHeight;
int rows;
int columns;
std::string GetTilePropertyStringAt(int layer, int x, int y, std::string );
float GetTilePropertyFloatAt(int layer, int x, int y, std::string );
int GetTilePropertyIntAt(int layer, int x, int y, std::string );
std::string GetObjectPropertyStringAt(int x, int y,std::string name);
float GetObjectPropertyFloatAt( int x, int y, std::string name);
int GetObjectPropertyIntAt(int x, int y, std::string name);
private:
bool GetTileSetFromGID(int tileGid, Tile &Tile);
//Used to offset the t ile number.
int firstTileID;
//Used to clip anything off screen.
sf::Rect <float> drawingBounds;
sf::Image TilesetImage;
//This stores all the solid areas (objects with type 'solid'). This gets checked by the IsSolid function.
std::vector <sf::Rect <int> > solidObjects;
//This stores all objects (including 'solid' types)
// std::vector <Object> objects;
std::map <int , std::map <int , Object> > XYObjects;
//This stores each layer of sprites/tiles so they can be drawn in order.
std::vector <Layer> layers;
//tilesets
//std::vector <TileSet> TileSets;
Tilesets m_Tilesets;
};
#endif // Map_H
/*********************************************************************
Quinn Schwab
16/08/2010
SFML Tiled Map Loader
The zlib license has been used to make this software fully compatible
with SFML. See http://www.sfml-dev.org/license.php
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include "MapLoader.h"
#include <iostream>
#include "tinyxml/tinyxml.h"
std::string Map::GetTilePropertyStringAt(int layer, int x, int y, std::string name)
{
return layers[layer].XYTiles[x][y].GetPropertyString(name);
}
float Map::GetTilePropertyFloatAt(int layer, int x, int y, std::string name)
{
return layers[layer].XYTiles[x][y].GetPropertyFloat(name);
}
int Map::GetTilePropertyIntAt(int layer, int x, int y, std::string name)
{
return layers[layer].XYTiles[x][y].GetPropertyInt(name);
}
std::string Map::GetObjectPropertyStringAt(int x, int y, std::string name)
{
return XYObjects[x][y].GetPropertyString(name);
}
float Map::GetObjectPropertyFloatAt(int x, int y, std::string name)
{
return XYObjects[x][y].GetPropertyFloat(name);
}
int Map::GetObjectPropertyIntAt(int x, int y, std::string name)
{
return XYObjects[x][y].GetPropertyInt(name);
}
int Object::GetPropertyInt(std::string name)
{
int i;
i = atoi(properties[name].c_str());
return i;
}
float Object::GetPropertyFloat(std::string name)
{
float f;
f = (float)strtod(properties[name].c_str(), NULL);
return f;
}
std::string Object::GetPropertyString(std::string name)
{
return properties[name];
}
int Layer::GetPropertyInt(std::string name)
{
int i;
i = atoi(properties[name].c_str());
return i;
}
float Layer::GetPropertyFloat(std::string name)
{
float f;
f = (float)strtod(properties[name].c_str(), NULL);
return f;
}
std::string Layer::GetPropertyString(std::string name)
{
return properties[name];
}
int Tile::GetPropertyInt(std::string name)
{
int i;
i = atoi(properties[name].c_str());
return i;
}
float Tile::GetPropertyFloat(std::string name)
{
float f;
f = (float)strtod(properties[name].c_str(), NULL);
return f;
}
std::string Tile::GetPropertyString(std::string name)
{
return properties[name];
}
int TileSet::GetPropertyInt(std::string name)
{
int i;
i = atoi(properties[name].c_str());
return i;
}
float TileSet::GetPropertyFloat(std::string name)
{
float f;
f = (float)strtod(properties[name].c_str(), NULL);
return f;
}
std::string TileSet::GetPropertyString(std::string name)
{
return properties[name];
}
Map::Map()
{
//ctor
}
Map::~Map()
{
//dtor
}
bool Map::LoadFromFile(std::string filename)
{
TiXmlDocument MapFile(filename.c_str());
if (!MapFile.LoadFile())
{
std::cout << "Loading Map \"" << filename << "\" failed." << std::endl;
return false;
}
//Map element. This is the root element for the whole file.
TiXmlElement *map;
map = MapFile.FirstChildElement("map");
//Set up misc map properties.
width = atoi(map->Attribute("width"));
height = atoi(map->Attribute("height"));
tileWidth = atoi(map->Attribute("tilewidth"));
tileHeight = atoi(map->Attribute("tileheight"));
columns = width / tileWidth;
rows = height / tileHeight;
//Tileset stuff
TiXmlElement *tilesetElement;
tilesetElement = map->FirstChildElement("tileset");
//while we want tilesets
while(tilesetElement)
{
TileSet TileSet;
TileSet.firstTileID = atoi(tilesetElement->Attribute("firstgid"));
//Tileset image
TiXmlElement *image;
image = tilesetElement->FirstChildElement("image");
TileSet.ImgSource = image->Attribute("source");
if (!TileSet.TilesetImage.LoadFromFile(TileSet.ImgSource))//Load the tileset image
{
std::cout << "Failed to load tile sheet." << std::endl;
return false;
}
TileSet.TilesetImage.CreateMaskFromColor(sf::Color(255, 0, 255));
TileSet.TilesetImage.SetSmooth(false);
if (tilesetElement->Attribute("spacing") != NULL)
{
TileSet.spacing = atoi(tilesetElement->Attribute("spacing"));
}
else
{
TileSet.spacing = 0;
}
if (tilesetElement->Attribute("margin") != NULL)
{
TileSet.margin = atoi(tilesetElement->Attribute("margin"));
}
else
{
TileSet.margin = 0;
}
if (tilesetElement->Attribute("tilewidth") != NULL)
{
TileSet.tileWidth = atoi(tilesetElement->Attribute("tilewidth"));
}
else
{
TileSet.tileWidth = 0;
}
if (tilesetElement->Attribute("tileheight") != NULL)
{
TileSet.tileHeight = atoi(tilesetElement->Attribute("tileheight"));
}
else
{
TileSet.tileHeight = 0;
}
//Columns and rows (of tileset image)
TileSet.width = TileSet.TilesetImage.GetWidth();
TileSet.height = TileSet.TilesetImage.GetHeight();
TileSet.columns = TileSet.width / TileSet.tileWidth;
TileSet.rows = TileSet.height / TileSet.tileHeight;
//do layer properties
TiXmlElement *properties;
properties = tilesetElement->FirstChildElement("properties");
if (properties != NULL)
{
TiXmlElement *prop;
prop = properties->FirstChildElement("property");
if (prop != NULL)
{
while(prop)
{
std::string propertyName = prop->Attribute("name");
std::string propertyValue = prop->Attribute("value");
TileSet.properties[propertyName] = propertyValue;
prop = prop->NextSiblingElement("property");
}
}
}
tilesetElement = tilesetElement->NextSiblingElement("tileset");
//set by tile first id
m_Tilesets[TileSet.firstTileID] = TileSet;
}
//Layers
TiXmlElement *layerElement;
layerElement = map->FirstChildElement("layer");
while (layerElement)
{
Layer layer;
if (layerElement->Attribute("opacity") != NULL)//check if opacity attribute exists
{
float opacity = (int)strtod(layerElement->Attribute("opacity"), NULL);//convert the (string) opacity element to float
layer.opacity = 255 * opacity;
}
else
{
layer.opacity = 255;//if the attribute doesnt exist, default to full opacity
}
//is visible
if (layerElement->Attribute("visible") != NULL)
{
layer.visible = atoi(layerElement->Attribute("opacity"));
}
else
{
layer.visible = 1;
}
//Tiles
TiXmlElement *layerDataElement;
layerDataElement = layerElement->FirstChildElement("data");
if (layerDataElement == NULL)
{
std::cout << "Bad map. No layer information found." << std::endl;
}
TiXmlElement *tileElement;
tileElement = layerDataElement->FirstChildElement("tile");
if (tileElement == NULL)
{
std::cout << "Bad map. No tile information found." << std::endl;
return false;
}
int x = 0;
int y = 0;
bool GotTileSet = false;
while (tileElement)
{
int tileGID = atoi(tileElement->Attribute("gid"));
Tile CurrentTile;
sf::Rect <int> rect;
CurrentTile.id = tileGID;
if(!GotTileSet)
{
GotTileSet = GetTileSetFromGID(tileGID, CurrentTile);
if(!GotTileSet)
{
std::cout << "Could not find tileset for tile." << std::endl;
return false;
}
//get the layers rows, They should really match the tileset.
//unless someone used half size tileset..EEK? but I won so I'm not worried! ha
layer.rows = CurrentTile.tileset->rows;
layer.columns = CurrentTile.tileset->columns;
}
//Work out the subrect ID to 'chop up' the tilesheet image.
CurrentTile.tilesettileid = tileGID - CurrentTile.tileset->firstTileID;
if(CurrentTile.tilesettileid >= 0)//we only need to (and only can) create a sprite/tile if there is one to display
{
CurrentTile.drawable = true;
CurrentTile.sprite.SetImage(CurrentTile.tileset->TilesetImage);
rect.Top = y * CurrentTile.tileset->tileHeight;
rect.Bottom = y * CurrentTile.tileset->tileHeight + CurrentTile.tileset->tileHeight;
rect.Left = x * CurrentTile.tileset->tileWidth;
rect.Right = x * CurrentTile.tileset->tileWidth + CurrentTile.tileset->tileWidth;
CurrentTile.sprite.SetSubRect( rect );
CurrentTile.sprite.SetPosition(x * CurrentTile.tileset->tileWidth, y * CurrentTile.tileset->tileHeight);
CurrentTile.sprite.SetColor(sf::Color(255, 255, 255, layer.opacity));//Set opacity of the tile.
}
else
{
CurrentTile.drawable = false;
}
//set the position
sf::Vector2i position;
position.x = x;
position.y = x;
CurrentTile.position = position;
sf::Vector2f coords;
coords.x = x * CurrentTile.tileset->tileWidth;
coords.y = x * CurrentTile.tileset->tileHeight;
CurrentTile.coords = coords;
//do tiles properties
TiXmlElement *properties;
properties = tileElement->FirstChildElement("properties");
if(properties != NULL)
{
TiXmlElement *prop;
prop = properties->FirstChildElement("property");
if(prop != NULL)
{
while(prop)
{
std::string propertyName = prop->Attribute("name");
std::string propertyValue = prop->Attribute("value");
CurrentTile.properties[propertyName] = propertyValue;
prop = prop->NextSiblingElement("property");
}
}
}
//Link tile x, y to this tile
layer.XYTiles[x].insert(std::pair<int, Tile>(y, CurrentTile));
//add tile to layer
// layer.tiles.push_back(layer.XYTiles);
tileElement = tileElement->NextSiblingElement("tile");
//increment x, y
x++;
if (x >= width)//if x has "hit" the end (right) of the map, reset it to the start (left)
{
x = 0;
y++;
if (y >= height)
{
y = 0;
}
}
}
//do layer properties
TiXmlElement *properties;
properties = layerElement->FirstChildElement("properties");
if (properties != NULL)
{
TiXmlElement *prop;
prop = properties->FirstChildElement("property");
if (prop != NULL)
{
while(prop)
{
std::string propertyName = prop->Attribute("name");
std::string propertyValue = prop->Attribute("value");
layer.properties[propertyName] = propertyValue;
prop = prop->NextSiblingElement("property");
}
}
}
layers.push_back(layer);
layerElement = layerElement->NextSiblingElement("layer");
}
//Objects
TiXmlElement *objectGroupElement;
if (map->FirstChildElement("objectgroup") != NULL)//Check that there is atleast one object layer
{
objectGroupElement = map->FirstChildElement("objectgroup");
while (objectGroupElement)//loop through object layers
{
TiXmlElement *objectElement;
objectElement = objectGroupElement->FirstChildElement("object");
while (objectElement)//loop through objects
{
std::string objectType;
int width = 0;
int height = 0;
if (objectElement->Attribute("type") != NULL)
{
objectType = objectElement->Attribute("type");
}
std::string objectName;
if (objectElement->Attribute("name") != NULL)
{
objectName = objectElement->Attribute("name");
}
int x = atoi(objectElement->Attribute("x"));
int y = atoi(objectElement->Attribute("y"));
if (objectElement->Attribute("width") != NULL)
{
width = atoi(objectElement->Attribute("width"));
}
if (objectElement->Attribute("height") != NULL)
{
height = atoi(objectElement->Attribute("height"));
}
Object object;
object.name = objectName;
object.type = objectType;
sf::Rect <int> objectRect;
objectRect.Top = y;
objectRect.Left = x;
objectRect.Bottom = y + height;
objectRect.Right = x + width;
x = x / tileWidth;
y = y / tileHeight;
if (objectType == "solid")
{
solidObjects.push_back(objectRect);
}
object.rect = objectRect;
TiXmlElement *properties;
properties = objectElement->FirstChildElement("properties");
if (properties != NULL)
{
TiXmlElement *prop;
prop = properties->FirstChildElement("property");
if (prop != NULL)
{
while(prop)
{
std::string propertyName = prop->Attribute("name");
std::string propertyValue = prop->Attribute("value");
object.properties[propertyName] = propertyValue;
prop = prop->NextSiblingElement("property");
}
}
}
// objects.push_back(object);
XYObjects[x][y] = object;
objectElement = objectElement->NextSiblingElement("object");
}
objectGroupElement = objectGroupElement->NextSiblingElement("objectgroup");
}
}
else
{
std::cout << "No object layers found..." << std::endl;
}
return true;
}
void Map::SetDrawingBounds(sf::Rect<float> bounds)
{
drawingBounds = bounds;
//Adjust the rect so that tiles are drawn just off screen, so you don't see them disappearing.
drawingBounds.Top -= tileHeight;
drawingBounds.Left -= tileWidth;
}
bool Map::GetTileSetFromGID(int tileGid, Tile &Tile)
{
//sort the tile sets by their first id
//std::sort(TileSets.begin(), TileSets.end(), comp_hungarian_strings);
//Tilesets, loop through and find
// if the ID (I) if bigger than tilegid, its not the one we want.
// if its lower, its the first lowest.
Tilesets::reverse_iterator iter;
for ( iter = m_Tilesets.rbegin();
iter != m_Tilesets.rend(); iter++)
{
//if the tileset ID is bigger
if(iter->first > tileGid)
{
continue;
}
//this is the highest ID, lower than our ID
Tile.tileset = &m_Tilesets[1];
return true;
}
return false;
}
void Map::Draw(sf::RenderWindow &window)
{
for (int layer = 0; layer < layers.size(); layer++)
{
for (int x = 0; x < layers[layer].columns; x++)
{
for (int y = 0; y < layers[layer].rows; y++)
{
window.Draw(layers[layer].XYTiles[x][y].sprite);
}
}
}
return;
/*
for (int x = 0; x < layers[layer].columns; x++)
{
for (int y = 0; y < layers[layer].rows; y++)
{
std::map <int , std::map <int , Tile> >::iterator XTile;
std::map <int , Tile>::const_iterator YTile;
XTile = layers[layer].XYTiles.find(x);
window.Draw(YTile->second.sprite);
}
}
std::map <int , std::map <int , Tile> >::const_iterator iter;
for ( iter = layers[layer].XYTiles.begin(); iter != layers[layer].XYTiles.end(); iter++)
{
std::map <int , Tile>::const_iterator tilesy;
for (tilesy = iter->second.begin(); tilesy != iter->second.end(); tilesy++)
{
window.Draw(tilesy->second.sprite);
}
}
for (int tile = 0; tile < layers[layer].tiles.size(); tile++)
{
// if( drawingBounds.Contains(layers[layer].tiles[tile].position.x, layers[layer].tiles[tile].position.y) )
{
if(layers[layer].tiles[tile].drawable)
{
window.Draw(layers[layer].tiles[tile].sprite);
}
}
}
*/
sf::Sprite sprite;
sf::Rect <int> rect;
rect.Top = 0 * m_Tilesets[1].tileHeight;
rect.Bottom = 0 * m_Tilesets[1].tileHeight + m_Tilesets[1].tileHeight;
rect.Left = 0 * m_Tilesets[1].tileWidth;
rect.Right = 0 * m_Tilesets[1].tileWidth + m_Tilesets[1].tileWidth;
sprite.SetImage(m_Tilesets[1].TilesetImage);
sprite.SetSubRect( rect );
sprite.SetPosition(0, 0);
window.Draw(sprite);
}