libgdx, Tiled Map Editor, TexturePacker, and you

libgdx has support for files created by the Tiled Map Editor, allowing you to easily create 2D orthogonal map scenes for your games. Once it’s up and running it is really neat. Getting it all to work requires careful setup, however. Updated 2011-03-20 after finding a critical problem found while following these steps again.

Here are the steps I followed, more or less:

  1. Download and install Tiled Map Editor. Don’t use it yet (unless you just want to play around.)
  2. Download and build TexturePacker.java in to a jar file.
    1. The latest committed source version can be found here: TexturePacker.java.
    2. In Eclipse, create a new Java project and call it “TexturePacker”. Add the usual libgdx libraries (gdx.jar, gdx-natives.jar, gdx-backend-jogl.jar, gdx-backend-jogl-natives.jar)
    3. Create a package in the project called “com.badlogic.gdx.imagepacker”
    4. Import TexturePacker.java by right clicking on the project and choosing “Import”. Ensure that the correct folder is displayed on the Import dialog.
    5. IMPORTANT: Added 2011-03-20. The settings in the existing TexturePacker.java file don’t seem to generate something you can easily use in Tiled Map Editor. I think it was made for packing “generic” textures for use in games. Modify the static Settings class as follows:
      652
      653
      654
      655
      656
      657
      658
      659
      660
      661
      662
      663
      664
      665
      666
      667
      668
      669
      670
      671
      672
      673
      
              static public class Settings {
                      public Format defaultFormat = Format.RGBA8888;
                      public TextureFilter defaultFilterMin = TextureFilter.Nearest;
                      public TextureFilter defaultFilterMag = TextureFilter.Nearest;
                      public int alphaThreshold = 0;
                      public boolean pot = true;
                      public int padding = 0;
                      public boolean duplicatePadding = true;
                      public boolean debug = false;
                      public boolean rotate = false;
                      public int minWidth = 16;
                      public int minHeight = 16;
                      public int maxWidth = 512;
                      public int maxHeight = 512;
                      public boolean stripWhitespace = false;
                      public boolean incremental=true;
                      public boolean alias;
       
                      HashMap<string , Long> crcs = new HashMap();
                      HashMap</string><string , String> packSections = new HashMap();
              }
      </string>
    6. Build and run the project. You should see “Usage: INPUTDIR OUTPUTDIR” in the console.
    7. Export the package as a Runnable JAR file, making sure to select your new “TexturePacker” launch configuration and “Package required libraries into generated JAR” library handling.
    8. You should now have a TexturePacker.jar file somewhere. Keep its location handy.
  3. Create your tile images. Use whatever png editor you like. This is key: Save each of your tile images in to a separate file of equal dimensions, using a common prefix and sequential numbering. For example, tile_1.png, tile_2.png, and so on. Save them all in a new directory. I’ll call it “tiles” here. You will need this common prefix later.
  4. Build your packed tile png:
    1. Open a command prompt and navigate to a directory just below the directory you saved your tiles to.
    2. Run: java -jar /path/to/TexturePacker.jar tiles output
    3. You should now have two files in output: “pack” and “tmp1.png”.
  5. Open Tiled Map Editor and create your game map:
    1. Create a new map. Set the map size to whatever you would like, and the tile size to whatever your tile dimensions are.
    2. In the Map menu, choose “New tileset”. Navigate to your new tmp1.png file.
    3. Paint your map — this is the most enjoyable part of the process, IMO.
    4. Save your map. Use the same prefix you used for your tile images. This is also key. If you name it something else libgdx will not be able to find your map’s images (unless you’re feeling adventurous, see below). I’ll call it “tiles.tmx” here.
  6. Create a standard libgdx project pair, create data and assets/data in your projects, and copy in your new pack, tmp1.png, and tiles.tmx files.
  7. If you just want to see if the map loaded, try this class. It is essentially a copy of TiledMapTest without being a subclass of GdxTest.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    
    package com.example.PlayingWithTME;
     
    import com.badlogic.gdx.ApplicationListener;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.InputAdapter;
    import com.badlogic.gdx.files.FileHandle;
    import com.badlogic.gdx.graphics.GL10;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.g2d.tiled.TileAtlas;
    import com.badlogic.gdx.graphics.g2d.tiled.TiledLoader;
    import com.badlogic.gdx.graphics.g2d.tiled.TiledMap;
    import com.badlogic.gdx.graphics.g2d.tiled.TiledMapRenderer;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
     
    public class PlayingWithTME implements ApplicationListener {
    	private TileAtlas tileAtlas;
    	private TiledMap map;
    	private TiledMapRenderer tiledMapRenderer;
     
    	private OrthographicCamera camera;
    	private OrthoCamController cameraController;
    	private Vector2 maxCamPosition = new Vector2(0, 0);
     
    	private static final int[] layersList = { 0 };
     
    	@Override
    	public void create() {
    		FileHandle packfile = Gdx.files.internal("data/pack");
    		FileHandle basedir = Gdx.files.internal("data");
     
    		map = TiledLoader.createMap(Gdx.files.internal("data/tiles.tmx"));
     
    		tileAtlas = new TileAtlas(map, packfile, basedir);
     
    		tiledMapRenderer = new TiledMapRenderer(map, tileAtlas,
    				Gdx.graphics.getWidth() / 3, Gdx.graphics.getHeight() / 3);
     
    		camera = new OrthographicCamera(Gdx.graphics.getWidth(),
    				Gdx.graphics.getHeight());
    		camera.position.set(tiledMapRenderer.getMapWidthPixels() / 2,
    				tiledMapRenderer.getMapHeightPixels() / 2, 0);
     
    		cameraController = new OrthoCamController(camera);
    		Gdx.input.setInputProcessor(cameraController);
     
    		maxCamPosition.set(tiledMapRenderer.getMapWidthPixels(),
    				tiledMapRenderer.getMapHeightPixels());
    	}
     
    	@Override
    	public void resume() {
    	}
     
    	@Override
    	public void render() {
    		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
     
    		camera.update();
     
    		tiledMapRenderer.getProjectionMatrix().set(camera.combined);
     
    		Vector3 tmp = new Vector3();
    		tmp.set(0, 0, 0);
    		camera.unproject(tmp);
     
    		tiledMapRenderer.render((int) tmp.x,
    				tiledMapRenderer.getMapHeightPixels() - (int) tmp.y,
    				Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), layersList);
    	}
     
    	@Override
    	public void resize(int width, int height) {
    	}
     
    	@Override
    	public void pause() {
    	}
     
    	@Override
    	public void dispose() {
    	}
     
    	public class OrthoCamController extends InputAdapter {
    		final OrthographicCamera camera;
    		final Vector3 curr = new Vector3();
    		final Vector3 last = new Vector3(-1, -1, 0);
    		final Vector3 delta = new Vector3();
     
    		public OrthoCamController(OrthographicCamera camera) {
    			this.camera = camera;
    		}
     
    		@Override
    		public boolean touchDragged(int x, int y, int pointer) {
    			camera.unproject(curr.set(x, y, 0));
    			if (!(last.x == -1 && last.y == -1 && last.z == -1)) {
    				camera.unproject(delta.set(last.x, last.y, 0));
    				delta.sub(curr);
    				camera.position.add(delta.x, delta.y, 0);
    			}
    			last.set(x, y, 0);
    			return false;
    		}
     
    		@Override
    		public boolean touchUp(int x, int y, int pointer, int button) {
    			last.set(-1, -1, -1);
    			return false;
    		}
    	}
    }

    Feeling adventurous?

    You can open up the tmx file and change the tile image name, and you could open the pack file and change all of the image names there (but if you do you’ll need to rename your tmx file), without having to rebuild everything from scratch. Want even more adventure? Subclass TileAtlas and change the constructor so that you can pass in a different prefix to the findRegions call. If you did that you could easily share textures across multiple tmx levels. Whoops, just noticed it uses private members. Well, there is another way to support multiple maps: put each .tmx file in separate directories, but use the same common base name you used when building the tiles and pack file. Works like a charm.

17 Comments

  1. Justin Hand says:

    I was following this guide but for some reason whenever I run the PlayingWithTME file I keep getting a null pointer exception at line 37 when the renderer is given values. Any idea what could be causing this problem?

  2. dpk says:

    I’ve seen this error come up before. Here’s the stack trace I’m seeing, which I believe matches what you’re seeing:

    Caused by: java.lang.NullPointerException
    	at com.badlogic.gdx.graphics.g2d.tiled.TiledMapRenderer.addBlock(TiledMapRenderer.java:187)
    	at com.badlogic.gdx.graphics.g2d.tiled.TiledMapRenderer.(TiledMapRenderer.java:150)
    	at com.badlogic.gdx.graphics.g2d.tiled.TiledMapRenderer.(TiledMapRenderer.java:66)
    	at com.example.PlayingWithTME.PlayingWithTME.create(PlayingWithTME.java:37)
    	at com.badlogic.gdx.backends.jogl.JoglGraphics.init(JoglGraphics.java:65)
    	at com.sun.opengl.impl.GLDrawableHelper.init(GLDrawableHelper.java:72)
    	at javax.media.opengl.GLCanvas$InitAction.run(GLCanvas.java:418)
    	at com.sun.opengl.impl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:189)
    [....]
    

    Check your pack file — it is a plain text file that you should be able to open from within Eclipse. The name used inside of it needs to match the name of your .tmx file. You’ll see lines like:

    tiles
      rotate: false
      xy: 0, 0
      size: 32, 32
      orig: 32, 32
      offset: 0, 0
      index: 9
    tiles
      rotate: false
      ...
    

    except instead of “tiles” you might see some other name. Whatever filename you’ve chosen for your tmx file, the name without the extension should be in the pack file. So if your file’s name is level.tmx, your pack file needs to be like:

    level
      rotate: false
      xy: 0, 0
      size: 32, 32
    ...
    

    Hope this helps!

  3. Justin Hand says:

    Thanks a lot! I had a typo in one of my tile names. I’ve got it working with tiles that are 32×32, but for some reason when I use a larger tile like 128×128 the tiles end up drawn in the wrong locations. Is there a limit to the tile size, or should I have specified the tile size somewhere in the Texture Packer.
    Thanks again, I really appreciate the help

  4. dpk says:

    I’m not sure about an upper limit, but I was able to make a 128×128 tile set work OK. Is it possible that the map is still working with the original 32×32 pack file?

  5. Justin Hand says:

    I’ve been looking through the pack file trying to figure out what could be causing this, and I’ve ran across a few places where multiple tiles are being given the same xy location.
    for example
    Tile
    rotate: false
    xy: 384, 256
    size: 128, 128
    orig: 128, 128
    offset: 0, 0
    index: 4
    Tile
    rotate: false
    xy: 384, 256
    size: 128, 128
    orig: 128, 128
    offset: 0, 0
    index: 2

    also when I run the texture packer I have been getting a message that says “Pixels eliminated” followed by a percentage equal to two tiles which suggests to me that at least two tiles that are showing up on the png file are not making it into the pack file which would lead to my tiles being jumbled. Have you ever seen anything like this?

  6. dpk says:

    I think I have seen that. The resultant image file ends up being something other than a multiple of your tile width and height because the packer is being smart about it and eliminating unnecessary whitespace. The settings I suggested are supposed to help you avoid that, though.

    I’ve been thinking about this for a while and I haven’t come up with a good solution that involves TexturePacker. I get the impression TexturePacker was designed more for standard sprite textures than for map tiles. I think I will re-write the post, replacing the TexturePacker references with a description of how to create the pack file by hand. I did that for a tileset on a recent project and so far, so good.

    Here’s a brief description of what I did:

    First, I used a paint program to draw the tiles all on a single image. In my case the tiles are 32×32 and I used a 512×512 pixel image to give me plenty of room to work with (for the map, anyway). Then, for the pack file, I set the size and orig to the same values (32, 32 in my case, 128, 128 in your case), used “0, 0″ for the offset, and then manually entered the x & y coordinates and the index. The tile index starts at 1 and the tiles are entered starting with the top left. This is copied directly from my pack file:

    
    tiles.png
    format: RGBA8888
    filter: Nearest,Nearest
    repeat: none
    level
      rotate: false
      xy: 0, 0
      size: 32, 32
      orig: 32, 32
      offset: 0, 0
      index: 1
    level
      rotate: false
      xy: 32, 0
      size: 32, 32
      orig: 32, 32
      offset: 0, 0
      index: 2
    level
      rotate: false
    

    [...]

    level
      rotate: false
      xy: 224, 0
      size: 32, 32
      orig: 32, 32
      offset: 0, 0
      index: 8
    level
      rotate: false
      xy: 0, 32
      size: 32, 32
      orig: 32, 32
      offset: 0, 0
      index: 9
    

    In this case my tmx file named “level.tmx”, so I had to use “level” in the pack file. Hopefully this “manual” method will help you. Let me know if it does and I’ll go ahead with the planned updates to this post.

  7. BTM says:

    Hi – great tutorials!

    I’m having some problems using TexturePacker. I’m trying with your source files and can’t get the jar to generate correct output file. All I’ve got in the generated pack file is:

    input1.png
    format: RGBA8888
    filter: Nearest,Nearest
    repeat: none
    level
    rotate: false
    xy: 0, 0
    size: 128, 256
    orig: 128, 256
    offset: 0, 0
    index: -1

    It’s like it is seeing the whole texture as a single tile. Any idea what can be the problem? The same is also generated when using the original TexturePacker binnary :/

  8. BTM says:

    Ok … I think I’ve been doing it wrong. TexturePacker generates a combined texture of all the separate tiles + the packfile which maps the tiles, right? This is kind of stupid as in result I would need to slice the tileset I’m already using first – correct? Is there a way to just use a .tmx and .png file with libgdx?

    • dpk says:

      That’s correct, and there is a way to work with an existing packed image, but you have to make the pack file yourself. Alternatively, you can use a tool like Gimp’s Guillotine to divide your image in to pieces, but that is a pain, too.

      • BTM says:

        Thanks for your help – I’ve writen my own TileAtlas which lets me skip the packfile (http://pastebin.com/iXTzcdJr) – no idea how it will work out in real life scenario, but during my testing it works OK. Now the problem I’m facing is that sometimes when moving there will be “cracks” in the tilemap – vertical lines at the edge of each tilemap. But I believ it’s due to not using bleeding, as when I change it back to your tilemap (from Jumper) the cracks dont show up.

        • dpk says:

          Ahh, yup. That’ll happen. You won’t see that bleeding on every platform, but it shows up on enough phones/tablets that it’s worth fixing.

  9. phil says:

    Hey david

    i am using your tiledmaphelper source code.
    i have the problem that evrything looks fine as long as the resolution ist above 399 x 291. if the resolution is <= 399 x 291 then only half of the tilemap gets displayed.

    is it possible that there is a bug in your code?
    any ideas??

    you can check it out by playing with the runnable jar (for desktop pc via libgdx) of my bubble bobble prototype:
    just scale the window…

    http://freigabe.philweb.de/BB/2012-03-07_BubbleBobble_399x292.jar

    any help apreciated!
    best regards & thx for the good tutorials/tools!
    phil

  10. phil says:

    p.s.:
    jumpertutorial has the same problem, but only horizontally….

  11. phil says:

    this is it:

    it works, when i change:

    tileMapRenderer = new TileMapRenderer(map, tileAtlas, 16, 16);

    to:
    tileMapRenderer = new TileMapRenderer(map, tileAtlas, 32, 26);

    this parameter is: “tiles per block”… so i have to take my whole world as a block!?
    what does that mean? is this correct?

    [my world is defined by 25 meters a 32px/meter (=800px) / i have 32 x 26 tiles, my tilesize is 18x18 px (=576 px horiz.)]

  12. Sploo says:

    Blank screen.

    I created a new a project and used this class, made a new map, made sure all paths are correct, no errors, seems to work, but nothing is drawn.

    Anything I’m doing wrong?

Leave a Reply