Keeping data together as much as possible is usually a good thing for performance reasons. Yet, I wouldn’t worry too much about that early on.
Here is an incomplete list of data types that could be used to store a TMX map in C++:
#ifndef TILED_TYPES
#define TILED_TYPES
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace Tiled {
enum Orientation
{
Orthogonal,
Isometric,
Hexagonal
};
enum TileIdFlags
{
FlipHorizontal = 0x80000000,
FlipVertical = 0x40000000,
FlipAntiDiagonal = 0x20000000,
AllTileIdFlags = FlipHorizontal | FlipVertical | FlipAntiDiagonal,
};
enum ObjectShape
{
ShapeRectangle,
ShapeEllipse,
ShapePolygon,
ShapePolyline
};
enum LayerType
{
LayerTile,
LayerObject,
};
typedef std::map<std::string, std::string> Properties;
struct Frame
{
unsigned tileId;
unsigned duration;
};
struct Animation
{
std::vector<Frame> frames;
};
struct Terrain
{
std::string name;
Properties properties;
};
struct Tile
{
unsigned id;
unsigned terrain;
std::string imageFilename;
std::shared_ptr<Animation> animation;
Properties properties;
};
// todo: Consider using polymorphism for Tileset / ImageCollection
struct Tileset
{
std::string name;
std::string imageFilename;
unsigned firstTileId;
int tileWidth;
int tileHeight;
int tileSpacing;
int margin;
Properties properties;
std::map<unsigned, Tile> tiles;
std::vector<Terrain> terrains;
};
struct Point
{
float x;
float y;
};
struct TileLayer
{
int x;
int y;
int width;
int height;
std::vector<unsigned> tileIds;
};
struct Color
{
unsigned char r, g, b, a;
};
struct Object
{
std::string name;
std::string type;
ObjectShape shape;
unsigned tileId;
float x;
float y;
float width;
float height;
float rotation;
bool visible;
Properties properties;
std::vector<Point> points;
};
struct ObjectLayer
{
std::string name;
Color color;
std::vector<Object> objects;
};
struct Layer
{
LayerType type;
std::string name;
float opacity;
bool visible;
Point offset;
Properties properties;
std::shared_ptr<TileLayer> tileLayer;
std::shared_ptr<ObjectLayer> objectLayer;
// todo: image layer
};
struct Map
{
Orientation orientation;
int width;
int height;
int tileWidth;
int tileHeight;
Color backgroundColor;
std::vector<Tileset> tilesets;
std::vector<Layer> layers;
Tileset *findTileset(const std::string &name) noexcept;
Layer *findLayer(const std::string &name) noexcept;
Tile *findTile(unsigned tileId) noexcept;
};
} // namespace Tiled
#endif // TILED_TYPES
I wrote this in order to experiment with C++ code generation that would remove the need for having a map loader at all. Here’s an example of how the above classes could be initialized using C++11 (it works on clang++ 3.7 this way, but not with g++ 5.2, which says “sorry, unimplemented: non-trivial designated initializers not supported”):
#include "tiled.h"
#include <iostream>
using namespace Tiled;
static Map map {
.orientation = Orthogonal,
.width = 10,
.height = 10,
.tileWidth = 32,
.tileHeight = 32,
.backgroundColor = Color { 0, 0, 0, 0 },
.tilesets = {
Tileset {
.name = "desert",
.imageFilename = "desert.png",
.firstTileId = 1,
.tileWidth = 32,
.tileHeight = 32,
.tileSpacing = 1,
.margin = 1,
.properties = {},
.tiles = {
{
1,
Tile {
.id = 1,
.terrain = 0,
},
},
{
2,
Tile {
.id = 2,
.terrain = 0,
},
},
},
.terrains = {},
},
},
.layers = {
Layer {
.type = LayerTile,
.name = "ground",
// todo: not supported by GCC 5.2
.tileLayer = std::shared_ptr<TileLayer>(new TileLayer {
.x = 0,
.y = 0,
.width = 10,
.height = 10,
.tileIds = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
},
}),
},
Layer {
.type = LayerObject,
// todo: not supported by GCC 5.2
.objectLayer = std::shared_ptr<ObjectLayer>(new ObjectLayer {
.objects = {
},
}),
},
},
};
int main()
{
std::cout << map.tilesets[0].name << " "
<< map.findTile(0) << " "
<< map.findTile(1)->id << std::endl;
return 0;
}
In any case, I guess my main point of advice is not to make things more complicated than necessary unless you have a good reason for it. It may very well be that the above code goes against my own advice though.