1 package com.badlogic.gdx.maps.tiled;
3 import java.io.IOException;
4 import java.util.StringTokenizer;
6 import com.badlogic.gdx.assets.AssetDescriptor;
7 import com.badlogic.gdx.assets.AssetLoaderParameters;
8 import com.badlogic.gdx.assets.AssetManager;
9 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
10 import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader;
11 import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
12 import com.badlogic.gdx.files.FileHandle;
13 import com.badlogic.gdx.graphics.Texture;
14 import com.badlogic.gdx.graphics.g2d.TextureRegion;
15 import com.badlogic.gdx.maps.ImageResolver;
16 import com.badlogic.gdx.maps.MapProperties;
17 import com.badlogic.gdx.maps.ImageResolver.AssetManagerImageResolver;
18 import com.badlogic.gdx.maps.ImageResolver.DirectImageResolver;
19 import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
20 import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;
21 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
22 import com.badlogic.gdx.utils.Array;
23 import com.badlogic.gdx.utils.GdxRuntimeException;
24 import com.badlogic.gdx.utils.ObjectMap;
25 import com.badlogic.gdx.utils.XmlReader;
26 import com.badlogic.gdx.utils.XmlReader.Element;
28 public class TideMapLoader extends SynchronousAssetLoader<TiledMap, TideMapLoader.Parameters> {
30 public static class Parameters extends AssetLoaderParameters<TiledMap> {
34 private XmlReader xml = new XmlReader();
37 public TideMapLoader() {
38 super(new InternalFileHandleResolver());
41 public TideMapLoader (FileHandleResolver resolver) {
45 public TiledMap load (String fileName) {
47 FileHandle tideFile = resolve(fileName);
48 root = xml.parse(tideFile);
49 ObjectMap<String, Texture> textures = new ObjectMap<String, Texture>();
50 for(FileHandle textureFile: loadTileSheets(root, tideFile)) {
51 textures.put(textureFile.path(), new Texture(textureFile));
53 DirectImageResolver imageResolver = new DirectImageResolver(textures);
54 TiledMap map = loadMap(root, tideFile, imageResolver);
55 map.setOwnedTextures(textures.values().toArray());
57 } catch(IOException e) {
58 throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
64 public TiledMap load (AssetManager assetManager, String fileName, Parameters parameter) {
65 FileHandle tideFile = resolve(fileName);
67 return loadMap(root, tideFile, new AssetManagerImageResolver(assetManager));
68 } catch (Exception e) {
69 throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
74 public Array<AssetDescriptor> getDependencies (String fileName, Parameters parameter) {
75 Array<AssetDescriptor> dependencies = new Array<AssetDescriptor>();
77 FileHandle tmxFile = resolve(fileName);
78 root = xml.parse(tmxFile);
79 for(FileHandle image: loadTileSheets(root, tmxFile)) {
80 dependencies.add(new AssetDescriptor(image.path(), Texture.class));
83 } catch (IOException e) {
84 throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
89 * Loads the map data, given the XML root element and an {@link ImageResolver} used
90 * to return the tileset Textures
91 * @param root the XML root element
92 * @param tmxFile the Filehandle of the tmx file
93 * @param imageResolver the {@link ImageResolver}
94 * @return the {@link TiledMap}
96 private TiledMap loadMap(Element root, FileHandle tmxFile, ImageResolver imageResolver) {
97 TiledMap map = new TiledMap();
98 Element properties = root.getChildByName("properties");
99 if (properties != null) {
100 loadProperties(map.getProperties(), properties);
102 Element tilesheets = root.getChildByName("TileSheets");
103 for (Element tilesheet : tilesheets.getChildrenByName("TileSheet")) {
104 loadTileSheet(map, tilesheet, tmxFile, imageResolver);
106 Element layers = root.getChildByName("Layers");
107 for (Element layer : layers.getChildrenByName("Layer")) {
108 loadLayer(map, layer);
115 * @param root the root XML element
116 * @return a list of filenames for images containing tiles
117 * @throws IOException
119 private Array<FileHandle> loadTileSheets(Element root, FileHandle tideFile) throws IOException {
120 Array<FileHandle> images = new Array<FileHandle>();
121 Element tilesheets = root.getChildByName("TileSheets");
122 for (Element tileset : tilesheets.getChildrenByName("TileSheet")) {
123 Element imageSource = tileset.getChildByName("ImageSource");
124 FileHandle image = getRelativeFileHandle(tideFile, imageSource.getText());
130 private void loadTileSheet(TiledMap map, Element element, FileHandle tideFile, ImageResolver imageResolver) {
131 if (element.getName().equals("TileSheet")) {
132 String id = element.getAttribute("Id");
133 String description = element.getChildByName("Description").getText();
134 String imageSource = element.getChildByName("ImageSource").getText();
136 Element alignment = element.getChildByName("Alignment");
137 String sheetSize = alignment.getAttribute("SheetSize");
138 String tileSize = alignment.getAttribute("TileSize");
139 String margin = alignment.getAttribute("Margin");
140 String spacing = alignment.getAttribute("Spacing");
142 String[] sheetSizeParts = sheetSize.split(" x ");
143 int sheetSizeX = Integer.parseInt(sheetSizeParts[0]);
144 int sheetSizeY = Integer.parseInt(sheetSizeParts[1]);
146 String[] tileSizeParts = tileSize.split(" x ");
147 int tileSizeX = Integer.parseInt(tileSizeParts[0]);
148 int tileSizeY = Integer.parseInt(tileSizeParts[1]);
150 String[] marginParts = margin.split(" x ");
151 int marginX = Integer.parseInt(marginParts[0]);
152 int marginY = Integer.parseInt(marginParts[1]);
154 String[] spacingParts = margin.split(" x ");
155 int spacingX = Integer.parseInt(spacingParts[0]);
156 int spacingY = Integer.parseInt(spacingParts[1]);
158 FileHandle image = getRelativeFileHandle(tideFile, imageSource);
159 TextureRegion texture = imageResolver.getImage(image.path());
161 // TODO: Actually load the tilesheet
162 // Need to make global ids as Tide doesn't have global ids.
163 TiledMapTileSets tilesets = map.getTileSets();
165 for (TiledMapTileSet tileset : tilesets) {
166 firstgid += tileset.size();
169 TiledMapTileSet tileset = new TiledMapTileSet();
171 tileset.getProperties().put("firstgid", firstgid);
174 int stopWidth = texture.getRegionWidth() - tileSizeX;
175 int stopHeight = texture.getRegionHeight() - tileSizeY;
177 for (int y = marginY; y <= stopHeight; y += tileSizeY + spacingY) {
178 for (int x = marginX; x <= stopWidth; x += tileSizeX + spacingX) {
179 TiledMapTile tile = new StaticTiledMapTile(new TextureRegion(texture, x, y, tileSizeX, tileSizeY));
181 tileset.putTile(gid++, tile);
185 Element properties = element.getChildByName("Properties");
186 if (properties != null) {
187 loadProperties(tileset.getProperties(), properties);
190 tilesets.addTileSet(tileset);
194 private void loadLayer(TiledMap map, Element element) {
195 if (element.getName().equals("Layer")) {
196 String id = element.getAttribute("Id");
197 String visible = element.getAttribute("Visible");
199 Element dimensions = element.getChildByName("Dimensions");
200 String layerSize = dimensions.getAttribute("LayerSize");
201 String tileSize = dimensions.getAttribute("TileSize");
203 String[] layerSizeParts = layerSize.split(" x ");
204 int layerSizeX = Integer.parseInt(layerSizeParts[0]);
205 int layerSizeY = Integer.parseInt(layerSizeParts[1]);
207 String[] tileSizeParts = tileSize.split(" x ");
208 int tileSizeX = Integer.parseInt(tileSizeParts[0]);
209 int tileSizeY = Integer.parseInt(tileSizeParts[1]);
211 TiledMapTileLayer layer = new TiledMapTileLayer(layerSizeX, layerSizeY, tileSizeX, tileSizeY);
212 Element tileArray = element.getChildByName("TileArray");
213 Array<Element> rows = tileArray.getChildrenByName("Row");
214 TiledMapTileSets tilesets = map.getTileSets();
215 TiledMapTileSet currentTileSet = null;
218 for (int row = 0, rowCount = rows.size; row < rowCount; row++) {
219 Element currentRow = rows.get(row);
220 y = rowCount -1 - row;
222 for (int child = 0, childCount = currentRow.getChildCount(); child < childCount; child++) {
223 Element currentChild = currentRow.getChild(child);
224 String name = currentChild.getName();
225 if (name.equals("TileSheet")) {
226 currentTileSet = tilesets.getTileSet(currentChild.getAttribute("Ref"));
227 firstgid = currentTileSet.getProperties().get("firstgid", Integer.class);
228 } else if (name.equals("Null")) {
229 x += currentChild.getIntAttribute("Count");
230 } else if (name.equals("Static")) {
231 Cell cell = new Cell();
232 cell.setTile(currentTileSet.getTile(firstgid + currentChild.getIntAttribute("Index")));
233 layer.setCell(x++, y, cell);
234 } else if (name.equals("Animated")) {
235 // Create an AnimatedTile
236 int interval = currentChild.getInt("Interval");
237 Element frames = currentChild.getChildByName("Frames");
238 Array<StaticTiledMapTile> frameTiles = new Array<StaticTiledMapTile>();
239 for (int frameChild = 0, frameChildCount = frames.getChildCount(); frameChild < frameChildCount; frameChild++) {
240 Element frame = frames.getChild(frameChild);
241 String frameName = frame.getName();
242 if (frameName.equals("TileSheet")) {
243 currentTileSet = tilesets.getTileSet(frame.getAttribute("Ref"));
244 firstgid = currentTileSet.getProperties().get("firstgid", Integer.class);
245 } else if (frameName.equals("Static")) {
246 frameTiles.add((StaticTiledMapTile) currentTileSet.getTile(firstgid + frame.getIntAttribute("Index")));
249 Cell cell = new Cell();
250 cell.setTile(new AnimatedTiledMapTile(interval / 1000f, frameTiles));
251 layer.setCell(x++, y, cell); //TODO: Reuse existing animated tiles
255 map.getLayers().add(layer);
259 private void loadProperties(MapProperties properties, Element element) {
260 if (element.getName().equals("Properties")) {
261 for (Element property : element.getChildrenByName("Property")) {
262 String key = property.getAttribute("Key", null);
263 String type = property.getAttribute("Type", null);
264 String value = property.getText();
266 if (type.equals("Int32")) {
267 properties.put(key, Integer.parseInt(value));
268 } else if (type.equals("String")) {
269 properties.put(key, value);
270 } else if (type.equals("Boolean")) {
271 properties.put(key, value.equalsIgnoreCase("true"));
273 properties.put(key, value);
279 private static FileHandle getRelativeFileHandle(FileHandle file, String path) {
280 StringTokenizer tokenizer = new StringTokenizer(path, "\\/");
281 FileHandle result = file.parent();
282 while (tokenizer.hasMoreElements()) {
283 String token = tokenizer.nextToken();
284 if (token.equals(".."))
285 result = result.parent();
287 result = result.child(token);