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