/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.maps.tiled; import com.badlogic.gdx.assets.AssetDescriptor; import com.badlogic.gdx.assets.AssetLoaderParameters; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.FileHandleResolver; import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader; import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.maps.ImageResolver; import com.badlogic.gdx.maps.ImageResolver.AssetManagerImageResolver; import com.badlogic.gdx.maps.ImageResolver.DirectImageResolver; import com.badlogic.gdx.maps.MapProperties; import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell; import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile; import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.XmlReader; import com.badlogic.gdx.utils.XmlReader.Element; import java.io.IOException; import java.util.StringTokenizer; public class TideMapLoader extends SynchronousAssetLoader<TiledMap, TideMapLoader.Parameters> { public static class Parameters extends AssetLoaderParameters<TiledMap> { } private XmlReader xml = new XmlReader(); private Element root; public TideMapLoader () { super(new InternalFileHandleResolver()); } public TideMapLoader (FileHandleResolver resolver) { super(resolver); } public TiledMap load (String fileName) { try { FileHandle tideFile = resolve(fileName); root = xml.parse(tideFile); ObjectMap<String, Texture> textures = new ObjectMap<String, Texture>(); for (FileHandle textureFile : loadTileSheets(root, tideFile)) { textures.put(textureFile.path(), new Texture(textureFile)); } DirectImageResolver imageResolver = new DirectImageResolver(textures); TiledMap map = loadMap(root, tideFile, imageResolver); map.setOwnedResources(textures.values().toArray()); return map; } catch (IOException e) { throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e); } } @Override public TiledMap load (AssetManager assetManager, String fileName, FileHandle tideFile, Parameters parameter) { try { return loadMap(root, tideFile, new AssetManagerImageResolver(assetManager)); } catch (Exception e) { throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e); } } @Override public Array<AssetDescriptor> getDependencies (String fileName, FileHandle tmxFile, Parameters parameter) { Array<AssetDescriptor> dependencies = new Array<AssetDescriptor>(); try { root = xml.parse(tmxFile); for (FileHandle image : loadTileSheets(root, tmxFile)) { dependencies.add(new AssetDescriptor(image.path(), Texture.class)); } return dependencies; } catch (IOException e) { throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e); } } /** Loads the map data, given the XML root element and an {@link ImageResolver} used to return the tileset Textures * @param root the XML root element * @param tmxFile the Filehandle of the tmx file * @param imageResolver the {@link ImageResolver} * @return the {@link TiledMap} */ private TiledMap loadMap (Element root, FileHandle tmxFile, ImageResolver imageResolver) { TiledMap map = new TiledMap(); Element properties = root.getChildByName("Properties"); if (properties != null) { loadProperties(map.getProperties(), properties); } Element tilesheets = root.getChildByName("TileSheets"); for (Element tilesheet : tilesheets.getChildrenByName("TileSheet")) { loadTileSheet(map, tilesheet, tmxFile, imageResolver); } Element layers = root.getChildByName("Layers"); for (Element layer : layers.getChildrenByName("Layer")) { loadLayer(map, layer); } return map; } /** Loads the tilesets * @param root the root XML element * @return a list of filenames for images containing tiles * @throws IOException */ private Array<FileHandle> loadTileSheets (Element root, FileHandle tideFile) throws IOException { Array<FileHandle> images = new Array<FileHandle>(); Element tilesheets = root.getChildByName("TileSheets"); for (Element tileset : tilesheets.getChildrenByName("TileSheet")) { Element imageSource = tileset.getChildByName("ImageSource"); FileHandle image = getRelativeFileHandle(tideFile, imageSource.getText()); images.add(image); } return images; } private void loadTileSheet (TiledMap map, Element element, FileHandle tideFile, ImageResolver imageResolver) { if (element.getName().equals("TileSheet")) { String id = element.getAttribute("Id"); String description = element.getChildByName("Description").getText(); String imageSource = element.getChildByName("ImageSource").getText(); Element alignment = element.getChildByName("Alignment"); String sheetSize = alignment.getAttribute("SheetSize"); String tileSize = alignment.getAttribute("TileSize"); String margin = alignment.getAttribute("Margin"); String spacing = alignment.getAttribute("Spacing"); String[] sheetSizeParts = sheetSize.split(" x "); int sheetSizeX = Integer.parseInt(sheetSizeParts[0]); int sheetSizeY = Integer.parseInt(sheetSizeParts[1]); String[] tileSizeParts = tileSize.split(" x "); int tileSizeX = Integer.parseInt(tileSizeParts[0]); int tileSizeY = Integer.parseInt(tileSizeParts[1]); String[] marginParts = margin.split(" x "); int marginX = Integer.parseInt(marginParts[0]); int marginY = Integer.parseInt(marginParts[1]); String[] spacingParts = margin.split(" x "); int spacingX = Integer.parseInt(spacingParts[0]); int spacingY = Integer.parseInt(spacingParts[1]); FileHandle image = getRelativeFileHandle(tideFile, imageSource); TextureRegion texture = imageResolver.getImage(image.path()); TiledMapTileSets tilesets = map.getTileSets(); int firstgid = 1; for (TiledMapTileSet tileset : tilesets) { firstgid += tileset.size(); } TiledMapTileSet tileset = new TiledMapTileSet(); tileset.setName(id); tileset.getProperties().put("firstgid", firstgid); int gid = firstgid; int stopWidth = texture.getRegionWidth() - tileSizeX; int stopHeight = texture.getRegionHeight() - tileSizeY; for (int y = marginY; y <= stopHeight; y += tileSizeY + spacingY) { for (int x = marginX; x <= stopWidth; x += tileSizeX + spacingX) { TiledMapTile tile = new StaticTiledMapTile(new TextureRegion(texture, x, y, tileSizeX, tileSizeY)); tile.setId(gid); tileset.putTile(gid++, tile); } } Element properties = element.getChildByName("Properties"); if (properties != null) { loadProperties(tileset.getProperties(), properties); } tilesets.addTileSet(tileset); } } private void loadLayer (TiledMap map, Element element) { if (element.getName().equals("Layer")) { String id = element.getAttribute("Id"); String visible = element.getAttribute("Visible"); Element dimensions = element.getChildByName("Dimensions"); String layerSize = dimensions.getAttribute("LayerSize"); String tileSize = dimensions.getAttribute("TileSize"); String[] layerSizeParts = layerSize.split(" x "); int layerSizeX = Integer.parseInt(layerSizeParts[0]); int layerSizeY = Integer.parseInt(layerSizeParts[1]); String[] tileSizeParts = tileSize.split(" x "); int tileSizeX = Integer.parseInt(tileSizeParts[0]); int tileSizeY = Integer.parseInt(tileSizeParts[1]); TiledMapTileLayer layer = new TiledMapTileLayer(layerSizeX, layerSizeY, tileSizeX, tileSizeY); layer.setName(id); layer.setVisible(visible.equalsIgnoreCase("True")); Element tileArray = element.getChildByName("TileArray"); Array<Element> rows = tileArray.getChildrenByName("Row"); TiledMapTileSets tilesets = map.getTileSets(); TiledMapTileSet currentTileSet = null; int firstgid = 0; int x, y; for (int row = 0, rowCount = rows.size; row < rowCount; row++) { Element currentRow = rows.get(row); y = rowCount - 1 - row; x = 0; for (int child = 0, childCount = currentRow.getChildCount(); child < childCount; child++) { Element currentChild = currentRow.getChild(child); String name = currentChild.getName(); if (name.equals("TileSheet")) { currentTileSet = tilesets.getTileSet(currentChild.getAttribute("Ref")); firstgid = currentTileSet.getProperties().get("firstgid", Integer.class); } else if (name.equals("Null")) { x += currentChild.getIntAttribute("Count"); } else if (name.equals("Static")) { Cell cell = new Cell(); cell.setTile(currentTileSet.getTile(firstgid + currentChild.getIntAttribute("Index"))); layer.setCell(x++, y, cell); } else if (name.equals("Animated")) { // Create an AnimatedTile int interval = currentChild.getInt("Interval"); Element frames = currentChild.getChildByName("Frames"); Array<StaticTiledMapTile> frameTiles = new Array<StaticTiledMapTile>(); for (int frameChild = 0, frameChildCount = frames.getChildCount(); frameChild < frameChildCount; frameChild++) { Element frame = frames.getChild(frameChild); String frameName = frame.getName(); if (frameName.equals("TileSheet")) { currentTileSet = tilesets.getTileSet(frame.getAttribute("Ref")); firstgid = currentTileSet.getProperties().get("firstgid", Integer.class); } else if (frameName.equals("Static")) { frameTiles.add((StaticTiledMapTile)currentTileSet.getTile(firstgid + frame.getIntAttribute("Index"))); } } Cell cell = new Cell(); cell.setTile(new AnimatedTiledMapTile(interval / 1000f, frameTiles)); layer.setCell(x++, y, cell); // TODO: Reuse existing animated tiles } } } Element properties = element.getChildByName("Properties"); if (properties != null) { loadProperties(layer.getProperties(), properties); } map.getLayers().add(layer); } } private void loadProperties (MapProperties properties, Element element) { if (element.getName().equals("Properties")) { for (Element property : element.getChildrenByName("Property")) { String key = property.getAttribute("Key", null); String type = property.getAttribute("Type", null); String value = property.getText(); if (type.equals("Int32")) { properties.put(key, Integer.parseInt(value)); } else if (type.equals("String")) { properties.put(key, value); } else if (type.equals("Boolean")) { properties.put(key, value.equalsIgnoreCase("true")); } else { properties.put(key, value); } } } } private static FileHandle getRelativeFileHandle (FileHandle file, String path) { StringTokenizer tokenizer = new StringTokenizer(path, "\\/"); FileHandle result = file.parent(); while (tokenizer.hasMoreElements()) { String token = tokenizer.nextToken(); if (token.equals("..")) result = result.parent(); else { result = result.child(token); } } return result; } }