1 ///Adds integration with the Tiled map editor 2 module dgt.level; 3 4 import std.file : readText; 5 import std.json; 6 import std.path : dirName; 7 8 import dgt.array, dgt.color, dgt.geom, dgt.texture, dgt.tilemap; 9 10 import dgt.io; 11 12 ///A layer of tiles represented by integer constants 13 struct TileLayer 14 { 15 string name; 16 Array!int tiles; 17 int offsetX, offsetY, widthInTiles, heightInTiles; 18 float opacity; 19 bool visible; 20 21 @nogc nothrow: 22 pure int opIndex(in int x, in int y) const 23 { 24 return tiles[x + y * widthInTiles]; 25 } 26 27 void destroy() 28 { 29 tiles.destroy(); 30 } 31 } 32 33 ///A layer of free-floating objects 34 struct EntityLayer 35 { 36 string name; 37 Array!Entity entities; 38 int offsetX, offsetY; 39 float opacity; 40 bool visible; 41 42 @nogc nothrow void destroy() 43 { 44 entities.destroy(); 45 } 46 } 47 48 ////A free-floating entity 49 struct Entity 50 { 51 string name, type; 52 int x, y, width, height, rotation; 53 bool flipX, flipY; 54 Texture tex; 55 bool visible; 56 } 57 58 private int number(in JSONValue json) 59 { 60 if(json.type == JSON_TYPE.INTEGER) 61 return cast(int)json.integer; 62 else 63 return cast(int)json.floating; 64 } 65 66 ///A structure to load the Tiled map into 67 struct Level 68 { 69 static immutable FLIPPED_HORIZONTALLY_FLAG = 0x80000000; 70 static immutable FLIPPED_VERTICALLY_FLAG = 0x40000000; 71 static immutable FLIPPED_DIAGONALLY_FLAG = 0x20000000; 72 73 74 private 75 { 76 Array!Texture sourceImages; 77 Array!Texture tileImages; 78 Array!TileLayer tileLayers; 79 Array!EntityLayer entityLayers; 80 } 81 82 ///Get the images used by the tiles and entities 83 @property const(Texture[]) images() const { return tileImages.array; } 84 ///Get the layers with tiles fixed to the grid 85 @property const(TileLayer[]) fixedTileLayers() const { return tileLayers.array; } 86 ///Get the layers with entities that can be freely moved 87 @property const(EntityLayer[]) freeEntityLayers() const { return entityLayers.array; } 88 89 int tileWidth, tileHeight, widthInTiles, heightInTiles; 90 91 private uint stripGID(uint gid, ref bool outFlipX, ref bool outFlipY) const 92 { 93 outFlipX = outFlipY = (gid & FLIPPED_DIAGONALLY_FLAG) != 0; 94 outFlipX = outFlipX != ((gid & FLIPPED_HORIZONTALLY_FLAG) != 0); 95 outFlipY = outFlipY != ((gid & FLIPPED_VERTICALLY_FLAG) != 0); 96 return gid & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG); 97 } 98 99 ///Load a Tiled map from a path in the filesystem 100 this(in string path, in int scale = 1) 101 { 102 sourceImages = Array!Texture(4); 103 tileImages = Array!Texture(16); 104 tileLayers = Array!TileLayer(4); 105 entityLayers = Array!EntityLayer(4); 106 107 auto pathToMap = dirName(path); 108 109 auto contents = parseJSON(readText(path)); 110 widthInTiles = contents["width"].number; 111 heightInTiles = contents["height"].number; 112 tileWidth = contents["tilewidth"].number; 113 tileHeight = contents["tileheight"].number; 114 foreach(tileset; contents["tilesets"].array) 115 { 116 auto image = Texture(pathToMap ~ "/" ~ tileset["image"].str); 117 sourceImages.add(image); 118 int margin = tileset["margin"].number; 119 int spacing = tileset["spacing"].number; 120 int width = tileset["tilewidth"].number; 121 int height = tileset["tileheight"].number; 122 for(int y = margin; y < image.sourceHeight - margin; y += height + spacing) 123 for(int x = margin; x < image.sourceWidth - margin; x += width + spacing) 124 tileImages.add(image.getSlice(Rectangle(x, y, width, height))); 125 } 126 127 foreach(layer; contents["layers"].array) 128 { 129 string name = layer["name"].str; 130 int offsetX = "offsetx" in layer ? layer["offsetx"].number : 0; 131 int offsetY = "offsety" in layer ? layer["offsety"].number : 0; 132 float opacity = layer["opacity"].type == JSON_TYPE.FLOAT ? layer["opacity"].floating : layer["opacity"].number; 133 bool visible = layer["visible"].type == JSON_TYPE.TRUE; 134 if(layer["type"].str == "tilelayer") 135 { 136 TileLayer tlayer = TileLayer(name, Array!int(layer["data"].array.length), 137 offsetX, offsetY, 138 layer["width"].number, 139 layer["height"].number, 140 opacity, visible); 141 foreach(tile; layer["data"].array) 142 tlayer.tiles.add(tile.number - 1); 143 tileLayers.add(tlayer); 144 } 145 else if(layer["type"].str == "objectgroup") 146 { 147 EntityLayer elayer = EntityLayer(name, Array!Entity(layer["objects"].array.length), 148 offsetX, offsetY, opacity, visible); 149 foreach(object; layer["objects"].array) 150 { 151 bool flipX, flipY; 152 uint gid = stripGID(cast(uint)object["gid"].number, flipX, flipY); 153 elayer.entities.add(Entity( 154 object["name"].str, 155 object["type"].str, 156 scale * object["x"].number, 157 scale * object["y"].number, 158 scale * object["width"].number, 159 scale * object["height"].number, 160 object["rotation"].number, 161 flipX, flipY, 162 tileImages[gid - 1], object["visible"].type == JSON_TYPE.TRUE 163 )); 164 } 165 entityLayers.add(elayer); 166 } 167 } 168 } 169 170 /** 171 Free all of the data in the map 172 173 Also destroys the textures the map loads, so be careful 174 */ 175 @nogc nothrow void destroy() 176 { 177 foreach(tex; sourceImages) 178 tex.destroy(); 179 foreach(layer; tileLayers) 180 layer.destroy(); 181 foreach(layer; entityLayers) 182 layer.destroy(); 183 sourceImages.destroy(); 184 tileImages.destroy(); 185 tileLayers.destroy(); 186 entityLayers.destroy(); 187 } 188 }