3

I am working on a Libgdx game which loads Tiled maps. The current map I am working on makes use of 2 tilesets, one for shadow/light and another for terrain and buildings. The general process I do, that has been working fine, is that I receive the sprite sheet from the artist, design the maps, then take the spritesheet file and split it using ImageMagick. From there I take the split images and create an optimized png and atlas file with TexturePacker.

However, this is the first map I have made that makes use of multiple tilesets. The issue I am having is when loading the map with AtlasTmxMapLoader it relies on a single atlas file property in the map. My shadows and lighting are split into a separate image and atlas and Id rather not merge them all into one in Tiled (and have to re-do a portion of the map).

Perhaps I am missing something simple. How can I handle multiple tilesets?

Arbel
  • 425
  • 8
  • 26

3 Answers3

3

So after reading more into how .tmx files are read I was able to fix my problem.

Here is how to properly do it when working with multiple tilesets and re-packing your spritesheets in TexturePacker. First, cut up the tileset images using a utility like ImageMagick and make sure they are indexed (specified by an underscore and number in the filename). You can do this with the crop command in ImageMagick like so:

convert.exe "shrine_tileset.png" -crop 16x16 "shrine_tileset_%02d.png"

Second, re-pack all tiles from all tilesets into a single atlas in TexturePacker. If it works correctly you will see the name of each tileset in the atlas file with an associated index based on the tile id. For example:

 shrine_tileset
  rotate: false
  xy: 382, 122
  size: 16, 16
  orig: 16, 16
  offset: 0, 0
  index: 703

Finally (and this is the part I could not figure out), make sure each tileset's tile indexes start from the "firstgid" value in the .tmx file. For example, my second tilesheet starts from 2049, as their are 2048 tiles in the first sheet. This should be denoted at the top of the .tmx file for each tileset.

<tileset firstgid="2049" source="shadow_light.tsx"/> 

So when cutting up the tiles for my tileset "shadow_light", I would start them from index 2048, one less than the gid, EX: "shadow_light_2048.png".

Hopefully this helps someone!

Arbel
  • 425
  • 8
  • 26
  • 1
    This worked for me in 1.9.9, thanks! tileID is being calculated in AtlasTmxMapLoader by: int tileId = firstgid + region.index, where region.index is the number suffix behind each cut up tile. Problem is libgdx is reading firstgid from the tsx file, not the tmx file, so it will just default to 1. So OP's solution fixes that by having the suffix be tileID-1. – Rimilel Dec 08 '19 at 00:42
2

I am no LibGDX expert but almost all tilemap renderers I've seen rely on working with 1 tileset. The reason is that they are rendered using OpenGL. The renderer sets the texture and draws all tiles with 1 draw call. You can't switch textures in between.

The best way would be to create 2 (or more) separate layers. Each layer uses 1 tileset. E.g. 1 for the background, 1 for the shadows, 1 for the foreground (e.g. walls).

Andreas Löw
  • 1,002
  • 8
  • 15
  • Thank you for the reply. I'm not sure I fully understand. My 2nd tileset is actually on a separate map layer only (the shadow/light layer). How can I setup the AtlasTmxMapLoader to work with both? – Arbel Feb 01 '16 at 00:13
  • I think you can't (... but as I already said: I am no LibGDX expert). You might be able to load 2 instances of your level with different atlases (you might need to patch the tmx file - or assign the atlases programmatically) and overlay them. With overlay I mean: Create 2 layers, attach them to the same parent node and display them as if they were a single level. – Andreas Löw Feb 02 '16 at 08:43
  • That sounds like it would be quite a mess. But it did get me thinking. One atlas is not an issue if I can somehow combine the images in Texture Packer into a single atlas that would work with libgdxs tile map renderer. If the name of the tiles match the tileset (and indexes) accordingly, it should work. However, I did test that and it didn't seem to draw my shadow/light layer. This has got to be a common thing though right? Multiple tilesets are a given on any large map. Id just like to avoid having to have a single tileset in Tiled. I dont mind it being combined into one outside of Tiled. – Arbel Feb 02 '16 at 12:36
1

This issue is fixed in 1.9.11. If you are using an earlier version you can override AtlasTmxMapLoader with a fix.

MyAtlasTmxMapLoader.Java

import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.maps.ImageResolver;
import com.badlogic.gdx.maps.MapProperties;
import com.badlogic.gdx.maps.tiled.AtlasTmxMapLoader;
import com.badlogic.gdx.maps.tiled.TiledMapTile;
import com.badlogic.gdx.maps.tiled.TiledMapTileSet;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.SerializationException;
import com.badlogic.gdx.utils.XmlReader.Element;
public class MyAtlasTmxMapLoader extends AtlasTmxMapLoader {
       /**
        * Same as AtlasTmxMapLoader, but fixed to get the firstid attribute from  the tileset element in the TMX file, not tsx file.
        */
       @Override
       protected void loadTileSet(Element mapElement, FileHandle tmxFile,  ImageResolver imageResolver) {
              if (mapElement.getName().equals("tileset")) {
                     String imageSource = "";
                     int imageWidth = 0;
                     int imageHeight = 0;
                     FileHandle image = null;
                     Element element = null;
                     String source = mapElement.getAttribute("source", null);
                     if (source != null) {
                           FileHandle tsx = getRelativeFileHandle(tmxFile,  source);
                           try {
                                  element = xml.parse(tsx);
                                  Element imageElement =  element.getChildByName("image");
                                  if (imageElement != null) {
                                         imageSource =  imageElement.getAttribute("source");
                                         imageWidth =  imageElement.getIntAttribute("width", 0);
                                         imageHeight =  imageElement.getIntAttribute("height", 0);
                                         image = getRelativeFileHandle(tsx,  imageSource);
                                  }
                           } catch (SerializationException e) {
                                  throw new GdxRuntimeException("Error parsing  external tileset.");
                           }
                     } else {
                           Element imageElement =  mapElement.getChildByName("image");
                           if (imageElement != null) {
                                  imageSource =  imageElement.getAttribute("source");
                                  imageWidth =  imageElement.getIntAttribute("width", 0);
                                  imageHeight =  imageElement.getIntAttribute("height", 0);
                                  image = getRelativeFileHandle(tmxFile,  imageSource);
                           }
                     }
                     String name = element.get("name", null);
                     // Get the firstid attribute from the tileset element in the  TMX file, not tsx file.
                     int firstgid = mapElement.getIntAttribute("firstgid", 1);
                     int tilewidth = element.getIntAttribute("tilewidth", 0);
                     int tileheight = element.getIntAttribute("tileheight", 0);
                     int spacing = element.getIntAttribute("spacing", 0);
                     int margin = element.getIntAttribute("margin", 0);
                     Element offset = element.getChildByName("tileoffset");
                     int offsetX = 0;
                     int offsetY = 0;
                     if (offset != null) {
                           offsetX = offset.getIntAttribute("x", 0);
                           offsetY = offset.getIntAttribute("y", 0);
                     }
                     TiledMapTileSet tileSet = new TiledMapTileSet();
                     // TileSet
                     tileSet.setName(name);
                     final MapProperties tileSetProperties =  tileSet.getProperties();
                     Element properties = element.getChildByName("properties");
                     if (properties != null) {
                           loadProperties(tileSetProperties, properties);
                     }
                     tileSetProperties.put("firstgid", firstgid);
                     // Tiles
                     Array<Element> tileElements =  element.getChildrenByName("tile");
                     addStaticTiles(tmxFile, imageResolver, tileSet, element,  tileElements, name, firstgid, tilewidth,
                                  tileheight, spacing, margin, source, offsetX,  offsetY, imageSource, imageWidth, imageHeight, image);
                     for (Element tileElement : tileElements) {
                           int localtid = tileElement.getIntAttribute("id", 0);
                           TiledMapTile tile = tileSet.getTile(firstgid +  localtid);
                           if (tile != null) {
                                  addTileProperties(tile, tileElement);
                                  addTileObjectGroup(tile, tileElement);
                                  addAnimatedTile(tileSet, tile, tileElement,  firstgid);
                           }
                     }
                     map.getTileSets().addTileSet(tileSet);
              }
       }
}

And then call:

new MyAtlasTmxMapLoader().load(pathname)

Source: [Tutorial] Using multiple Tilesets with Libgdx and Tiled

Rimilel
  • 133
  • 6