Home | History | Annotate | Download | only in tiled
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.maps.tiled;
     18 
     19 import com.badlogic.gdx.assets.AssetDescriptor;
     20 import com.badlogic.gdx.assets.AssetManager;
     21 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
     22 import com.badlogic.gdx.assets.loaders.TextureLoader;
     23 import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter;
     24 import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
     25 import com.badlogic.gdx.files.FileHandle;
     26 import com.badlogic.gdx.graphics.Texture;
     27 import com.badlogic.gdx.graphics.g2d.TextureRegion;
     28 import com.badlogic.gdx.maps.ImageResolver;
     29 import com.badlogic.gdx.maps.ImageResolver.AssetManagerImageResolver;
     30 import com.badlogic.gdx.maps.ImageResolver.DirectImageResolver;
     31 import com.badlogic.gdx.maps.MapProperties;
     32 import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
     33 import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;
     34 import com.badlogic.gdx.utils.Array;
     35 import com.badlogic.gdx.utils.GdxRuntimeException;
     36 import com.badlogic.gdx.utils.IntArray;
     37 import com.badlogic.gdx.utils.ObjectMap;
     38 import com.badlogic.gdx.utils.XmlReader.Element;
     39 import java.io.IOException;
     40 
     41 /** @brief synchronous loader for TMX maps created with the Tiled tool */
     42 public class TmxMapLoader extends BaseTmxMapLoader<TmxMapLoader.Parameters> {
     43 
     44 	public static class Parameters extends BaseTmxMapLoader.Parameters {
     45 
     46 	}
     47 
     48 	public TmxMapLoader () {
     49 		super(new InternalFileHandleResolver());
     50 	}
     51 
     52 	/** Creates loader
     53 	 *
     54 	 * @param resolver */
     55 	public TmxMapLoader (FileHandleResolver resolver) {
     56 		super(resolver);
     57 	}
     58 
     59 	/** Loads the {@link TiledMap} from the given file. The file is resolved via the {@link FileHandleResolver} set in the
     60 	 * constructor of this class. By default it will resolve to an internal file. The map will be loaded for a y-up coordinate
     61 	 * system.
     62 	 * @param fileName the filename
     63 	 * @return the TiledMap */
     64 	public TiledMap load (String fileName) {
     65 		return load(fileName, new TmxMapLoader.Parameters());
     66 	}
     67 
     68 	/** Loads the {@link TiledMap} from the given file. The file is resolved via the {@link FileHandleResolver} set in the
     69 	 * constructor of this class. By default it will resolve to an internal file.
     70 	 * @param fileName the filename
     71 	 * @param parameters specifies whether to use y-up, generate mip maps etc.
     72 	 * @return the TiledMap */
     73 	public TiledMap load (String fileName, TmxMapLoader.Parameters parameters) {
     74 		try {
     75 			this.convertObjectToTileSpace = parameters.convertObjectToTileSpace;
     76 			this.flipY = parameters.flipY;
     77 			FileHandle tmxFile = resolve(fileName);
     78 			root = xml.parse(tmxFile);
     79 			ObjectMap<String, Texture> textures = new ObjectMap<String, Texture>();
     80 			Array<FileHandle> textureFiles = loadTilesets(root, tmxFile);
     81 			textureFiles.addAll(loadImages(root, tmxFile));
     82 
     83 			for (FileHandle textureFile : textureFiles) {
     84 				Texture texture = new Texture(textureFile, parameters.generateMipMaps);
     85 				texture.setFilter(parameters.textureMinFilter, parameters.textureMagFilter);
     86 				textures.put(textureFile.path(), texture);
     87 			}
     88 
     89 			DirectImageResolver imageResolver = new DirectImageResolver(textures);
     90 			TiledMap map = loadTilemap(root, tmxFile, imageResolver);
     91 			map.setOwnedResources(textures.values().toArray());
     92 			return map;
     93 		} catch (IOException e) {
     94 			throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
     95 		}
     96 	}
     97 
     98 	@Override
     99 	public void loadAsync (AssetManager manager, String fileName, FileHandle tmxFile, TmxMapLoader.Parameters parameter) {
    100 		map = null;
    101 
    102 		if (parameter != null) {
    103 			convertObjectToTileSpace = parameter.convertObjectToTileSpace;
    104 			flipY = parameter.flipY;
    105 		} else {
    106 			convertObjectToTileSpace = false;
    107 			flipY = true;
    108 		}
    109 		try {
    110 			map = loadTilemap(root, tmxFile, new AssetManagerImageResolver(manager));
    111 		} catch (Exception e) {
    112 			throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
    113 		}
    114 	}
    115 
    116 	@Override
    117 	public TiledMap loadSync (AssetManager manager, String fileName, FileHandle file, TmxMapLoader.Parameters parameter) {
    118 		return map;
    119 	}
    120 
    121 	/** Retrieves TiledMap resource dependencies
    122 	 *
    123 	 * @param fileName
    124 	 * @param parameter not used for now
    125 	 * @return dependencies for the given .tmx file */
    126 	@Override
    127 	public Array<AssetDescriptor> getDependencies (String fileName, FileHandle tmxFile, Parameters parameter) {
    128 		Array<AssetDescriptor> dependencies = new Array<AssetDescriptor>();
    129 		try {
    130 			root = xml.parse(tmxFile);
    131 			boolean generateMipMaps = (parameter != null ? parameter.generateMipMaps : false);
    132 			TextureLoader.TextureParameter texParams = new TextureParameter();
    133 			texParams.genMipMaps = generateMipMaps;
    134 			if (parameter != null) {
    135 				texParams.minFilter = parameter.textureMinFilter;
    136 				texParams.magFilter = parameter.textureMagFilter;
    137 			}
    138 			for (FileHandle image : loadTilesets(root, tmxFile)) {
    139 				dependencies.add(new AssetDescriptor(image, Texture.class, texParams));
    140 			}
    141 			for (FileHandle image : loadImages(root, tmxFile)) {
    142 				dependencies.add(new AssetDescriptor(image, Texture.class, texParams));
    143 			}
    144 			return dependencies;
    145 		} catch (IOException e) {
    146 			throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
    147 		}
    148 	}
    149 
    150 	/** Loads the map data, given the XML root element and an {@link ImageResolver} used to return the tileset Textures
    151 	 * @param root the XML root element
    152 	 * @param tmxFile the Filehandle of the tmx file
    153 	 * @param imageResolver the {@link ImageResolver}
    154 	 * @return the {@link TiledMap} */
    155 	protected TiledMap loadTilemap (Element root, FileHandle tmxFile, ImageResolver imageResolver) {
    156 		TiledMap map = new TiledMap();
    157 
    158 		String mapOrientation = root.getAttribute("orientation", null);
    159 		int mapWidth = root.getIntAttribute("width", 0);
    160 		int mapHeight = root.getIntAttribute("height", 0);
    161 		int tileWidth = root.getIntAttribute("tilewidth", 0);
    162 		int tileHeight = root.getIntAttribute("tileheight", 0);
    163 		int hexSideLength = root.getIntAttribute("hexsidelength", 0);
    164 		String staggerAxis = root.getAttribute("staggeraxis", null);
    165 		String staggerIndex = root.getAttribute("staggerindex", null);
    166 		String mapBackgroundColor = root.getAttribute("backgroundcolor", null);
    167 
    168 		MapProperties mapProperties = map.getProperties();
    169 		if (mapOrientation != null) {
    170 			mapProperties.put("orientation", mapOrientation);
    171 		}
    172 		mapProperties.put("width", mapWidth);
    173 		mapProperties.put("height", mapHeight);
    174 		mapProperties.put("tilewidth", tileWidth);
    175 		mapProperties.put("tileheight", tileHeight);
    176 		mapProperties.put("hexsidelength", hexSideLength);
    177 		if (staggerAxis != null) {
    178 			mapProperties.put("staggeraxis", staggerAxis);
    179 		}
    180 		if (staggerIndex != null) {
    181 			mapProperties.put("staggerindex", staggerIndex);
    182 		}
    183 		if (mapBackgroundColor != null) {
    184 			mapProperties.put("backgroundcolor", mapBackgroundColor);
    185 		}
    186 		mapTileWidth = tileWidth;
    187 		mapTileHeight = tileHeight;
    188 		mapWidthInPixels = mapWidth * tileWidth;
    189 		mapHeightInPixels = mapHeight * tileHeight;
    190 
    191 		if (mapOrientation != null) {
    192 			if ("staggered".equals(mapOrientation)) {
    193 				if (mapHeight > 1) {
    194 					mapWidthInPixels += tileWidth / 2;
    195 					mapHeightInPixels = mapHeightInPixels / 2 + tileHeight / 2;
    196 				}
    197 			}
    198 		}
    199 
    200 		Element properties = root.getChildByName("properties");
    201 		if (properties != null) {
    202 			loadProperties(map.getProperties(), properties);
    203 		}
    204 		Array<Element> tilesets = root.getChildrenByName("tileset");
    205 		for (Element element : tilesets) {
    206 			loadTileSet(map, element, tmxFile, imageResolver);
    207 			root.removeChild(element);
    208 		}
    209 		for (int i = 0, j = root.getChildCount(); i < j; i++) {
    210 			Element element = root.getChild(i);
    211 			String name = element.getName();
    212 			if (name.equals("layer")) {
    213 				loadTileLayer(map, element);
    214 			} else if (name.equals("objectgroup")) {
    215 				loadObjectGroup(map, element);
    216 			}
    217 			else if (name.equals("imagelayer")) {
    218 				loadImageLayer(map, element, tmxFile, imageResolver);
    219 			}
    220 		}
    221 		return map;
    222 	}
    223 
    224 	/** Loads the tilesets
    225 	 * @param root the root XML element
    226 	 * @return a list of filenames for images containing tiles
    227 	 * @throws IOException */
    228 	protected Array<FileHandle> loadTilesets (Element root, FileHandle tmxFile) throws IOException {
    229 		Array<FileHandle> images = new Array<FileHandle>();
    230 		for (Element tileset : root.getChildrenByName("tileset")) {
    231 			String source = tileset.getAttribute("source", null);
    232 			if (source != null) {
    233 				FileHandle tsxFile = getRelativeFileHandle(tmxFile, source);
    234 				tileset = xml.parse(tsxFile);
    235 				Element imageElement = tileset.getChildByName("image");
    236 				if (imageElement != null) {
    237 					String imageSource = tileset.getChildByName("image").getAttribute("source");
    238 					FileHandle image = getRelativeFileHandle(tsxFile, imageSource);
    239 					images.add(image);
    240 				} else {
    241 					for (Element tile : tileset.getChildrenByName("tile")) {
    242 						String imageSource = tile.getChildByName("image").getAttribute("source");
    243 						FileHandle image = getRelativeFileHandle(tsxFile, imageSource);
    244 						images.add(image);
    245 					}
    246 				}
    247 			} else {
    248 				Element imageElement = tileset.getChildByName("image");
    249 				if (imageElement != null) {
    250 					String imageSource = tileset.getChildByName("image").getAttribute("source");
    251 					FileHandle image = getRelativeFileHandle(tmxFile, imageSource);
    252 					images.add(image);
    253 				} else {
    254 					for (Element tile : tileset.getChildrenByName("tile")) {
    255 						String imageSource = tile.getChildByName("image").getAttribute("source");
    256 						FileHandle image = getRelativeFileHandle(tmxFile, imageSource);
    257 						images.add(image);
    258 					}
    259 				}
    260 			}
    261 		}
    262 		return images;
    263 	}
    264 
    265 	/** Loads the images in image layers
    266 	 * @param root the root XML element
    267 	 * @return a list of filenames for images inside image layers
    268 	 * @throws IOException */
    269 	protected Array<FileHandle> loadImages (Element root, FileHandle tmxFile) throws IOException {
    270 		Array<FileHandle> images = new Array<FileHandle>();
    271 
    272 		for (Element imageLayer : root.getChildrenByName("imagelayer")) {
    273 			Element image = imageLayer.getChildByName("image");
    274 			String source = image.getAttribute("source", null);
    275 
    276 			if (source != null) {
    277 				FileHandle handle = getRelativeFileHandle(tmxFile, source);
    278 
    279 				if (!images.contains(handle, false)) {
    280 					images.add(handle);
    281 				}
    282 			}
    283 		}
    284 
    285 		return images;
    286 	}
    287 
    288 	/** Loads the specified tileset data, adding it to the collection of the specified map, given the XML element, the tmxFile and
    289 	 * an {@link ImageResolver} used to retrieve the tileset Textures.
    290 	 *
    291 	 * <p>
    292 	 * Default tileset's property keys that are loaded by default are:
    293 	 * </p>
    294 	 *
    295 	 * <ul>
    296 	 * <li><em>firstgid</em>, (int, defaults to 1) the first valid global id used for tile numbering</li>
    297 	 * <li><em>imagesource</em>, (String, defaults to empty string) the tileset source image filename</li>
    298 	 * <li><em>imagewidth</em>, (int, defaults to 0) the tileset source image width</li>
    299 	 * <li><em>imageheight</em>, (int, defaults to 0) the tileset source image height</li>
    300 	 * <li><em>tilewidth</em>, (int, defaults to 0) the tile width</li>
    301 	 * <li><em>tileheight</em>, (int, defaults to 0) the tile height</li>
    302 	 * <li><em>margin</em>, (int, defaults to 0) the tileset margin</li>
    303 	 * <li><em>spacing</em>, (int, defaults to 0) the tileset spacing</li>
    304 	 * </ul>
    305 	 *
    306 	 * <p>
    307 	 * The values are extracted from the specified Tmx file, if a value can't be found then the default is used.
    308 	 * </p>
    309 	 * @param map the Map whose tilesets collection will be populated
    310 	 * @param element the XML element identifying the tileset to load
    311 	 * @param tmxFile the Filehandle of the tmx file
    312 	 * @param imageResolver the {@link ImageResolver} */
    313 	protected void loadTileSet (TiledMap map, Element element, FileHandle tmxFile, ImageResolver imageResolver) {
    314 		if (element.getName().equals("tileset")) {
    315 			String name = element.get("name", null);
    316 			int firstgid = element.getIntAttribute("firstgid", 1);
    317 			int tilewidth = element.getIntAttribute("tilewidth", 0);
    318 			int tileheight = element.getIntAttribute("tileheight", 0);
    319 			int spacing = element.getIntAttribute("spacing", 0);
    320 			int margin = element.getIntAttribute("margin", 0);
    321 			String source = element.getAttribute("source", null);
    322 
    323 			int offsetX = 0;
    324 			int offsetY = 0;
    325 
    326 			String imageSource = "";
    327 			int imageWidth = 0, imageHeight = 0;
    328 
    329 			FileHandle image = null;
    330 			if (source != null) {
    331 				FileHandle tsx = getRelativeFileHandle(tmxFile, source);
    332 				try {
    333 					element = xml.parse(tsx);
    334 					name = element.get("name", null);
    335 					tilewidth = element.getIntAttribute("tilewidth", 0);
    336 					tileheight = element.getIntAttribute("tileheight", 0);
    337 					spacing = element.getIntAttribute("spacing", 0);
    338 					margin = element.getIntAttribute("margin", 0);
    339 					Element offset = element.getChildByName("tileoffset");
    340 					if (offset != null) {
    341 						offsetX = offset.getIntAttribute("x", 0);
    342 						offsetY = offset.getIntAttribute("y", 0);
    343 					}
    344 					Element imageElement = element.getChildByName("image");
    345 					if (imageElement != null) {
    346 						imageSource = imageElement.getAttribute("source");
    347 						imageWidth = imageElement.getIntAttribute("width", 0);
    348 						imageHeight = imageElement.getIntAttribute("height", 0);
    349 						image = getRelativeFileHandle(tsx, imageSource);
    350 					}
    351 				} catch (IOException e) {
    352 					throw new GdxRuntimeException("Error parsing external tileset.");
    353 				}
    354 			} else {
    355 				Element offset = element.getChildByName("tileoffset");
    356 				if (offset != null) {
    357 					offsetX = offset.getIntAttribute("x", 0);
    358 					offsetY = offset.getIntAttribute("y", 0);
    359 				}
    360 				Element imageElement = element.getChildByName("image");
    361 				if (imageElement != null) {
    362 					imageSource = imageElement.getAttribute("source");
    363 					imageWidth = imageElement.getIntAttribute("width", 0);
    364 					imageHeight = imageElement.getIntAttribute("height", 0);
    365 					image = getRelativeFileHandle(tmxFile, imageSource);
    366 				}
    367 			}
    368 
    369 			TiledMapTileSet tileset = new TiledMapTileSet();
    370 			tileset.setName(name);
    371 			tileset.getProperties().put("firstgid", firstgid);
    372 			if (image != null) {
    373 				TextureRegion texture = imageResolver.getImage(image.path());
    374 
    375 				MapProperties props = tileset.getProperties();
    376 				props.put("imagesource", imageSource);
    377 				props.put("imagewidth", imageWidth);
    378 				props.put("imageheight", imageHeight);
    379 				props.put("tilewidth", tilewidth);
    380 				props.put("tileheight", tileheight);
    381 				props.put("margin", margin);
    382 				props.put("spacing", spacing);
    383 
    384 				int stopWidth = texture.getRegionWidth() - tilewidth;
    385 				int stopHeight = texture.getRegionHeight() - tileheight;
    386 
    387 				int id = firstgid;
    388 
    389 				for (int y = margin; y <= stopHeight; y += tileheight + spacing) {
    390 					for (int x = margin; x <= stopWidth; x += tilewidth + spacing) {
    391 						TextureRegion tileRegion = new TextureRegion(texture, x, y, tilewidth, tileheight);
    392 						TiledMapTile tile = new StaticTiledMapTile(tileRegion);
    393 						tile.setId(id);
    394 						tile.setOffsetX(offsetX);
    395 						tile.setOffsetY(flipY ? -offsetY : offsetY);
    396 						tileset.putTile(id++, tile);
    397 					}
    398 				}
    399 			} else {
    400 				Array<Element> tileElements = element.getChildrenByName("tile");
    401 				for (Element tileElement : tileElements) {
    402 					Element imageElement = tileElement.getChildByName("image");
    403 					if (imageElement != null) {
    404 						imageSource = imageElement.getAttribute("source");
    405 						imageWidth = imageElement.getIntAttribute("width", 0);
    406 						imageHeight = imageElement.getIntAttribute("height", 0);
    407 						image = getRelativeFileHandle(tmxFile, imageSource);
    408 					}
    409 					TextureRegion texture = imageResolver.getImage(image.path());
    410 					TiledMapTile tile = new StaticTiledMapTile(texture);
    411 					tile.setId(firstgid + tileElement.getIntAttribute("id"));
    412 					tile.setOffsetX(offsetX);
    413 					tile.setOffsetY(flipY ? -offsetY : offsetY);
    414 					tileset.putTile(tile.getId(), tile);
    415 				}
    416 			}
    417 			Array<Element> tileElements = element.getChildrenByName("tile");
    418 
    419 			Array<AnimatedTiledMapTile> animatedTiles = new Array<AnimatedTiledMapTile>();
    420 
    421 			for (Element tileElement : tileElements) {
    422 				int localtid = tileElement.getIntAttribute("id", 0);
    423 				TiledMapTile tile = tileset.getTile(firstgid + localtid);
    424 				if (tile != null) {
    425 					Element animationElement = tileElement.getChildByName("animation");
    426 					if (animationElement != null) {
    427 
    428 						Array<StaticTiledMapTile> staticTiles = new Array<StaticTiledMapTile>();
    429 						IntArray intervals = new IntArray();
    430 						for (Element frameElement: animationElement.getChildrenByName("frame")) {
    431 							staticTiles.add((StaticTiledMapTile) tileset.getTile(firstgid + frameElement.getIntAttribute("tileid")));
    432 							intervals.add(frameElement.getIntAttribute("duration"));
    433 						}
    434 
    435 						AnimatedTiledMapTile animatedTile = new AnimatedTiledMapTile(intervals, staticTiles);
    436 						animatedTile.setId(tile.getId());
    437 						animatedTiles.add(animatedTile);
    438 						tile = animatedTile;
    439 					}
    440 
    441 					String terrain = tileElement.getAttribute("terrain", null);
    442 					if (terrain != null) {
    443 						tile.getProperties().put("terrain", terrain);
    444 					}
    445 					String probability = tileElement.getAttribute("probability", null);
    446 					if (probability != null) {
    447 						tile.getProperties().put("probability", probability);
    448 					}
    449 					Element properties = tileElement.getChildByName("properties");
    450 					if (properties != null) {
    451 						loadProperties(tile.getProperties(), properties);
    452 					}
    453 				}
    454 			}
    455 
    456 			for (AnimatedTiledMapTile tile : animatedTiles) {
    457 				tileset.putTile(tile.getId(), tile);
    458 			}
    459 
    460 			Element properties = element.getChildByName("properties");
    461 			if (properties != null) {
    462 				loadProperties(tileset.getProperties(), properties);
    463 			}
    464 			map.getTileSets().addTileSet(tileset);
    465 		}
    466 	}
    467 
    468 }
    469