Wrong ID's in tileset?


#1

Hello,

I’m using Tiled to make a map for my Java 2D sidescroller game and once I exported it and put it in my Eclipse .map file and played it, the ID’s were completely messed up. For example the ground is supposed to be grass but it’s dirt, because Tiled somehow makes it one more than it already is. Tiled says that the ID of my grass is 21 but when I open it in eclipse, it says the ID is 22.

Is there a way I could change the ID’s of tiles in Tiled? When I go to “Tile properties”, it won’t let me touch the settings.

Thank you in advance!


(Nathaniel Nielsen) #2

Tiled adds 1 to ID’s to allow 0 to be used as the ID for empty space. This is done partly because the the first three bits in the ID are used for orientation flags for flipping and rotation, so using a negative value for empty space wouldn’t make sense. If you see a really big number in your map file, you’ll need to mask the bits to get the orientation information and then remove them via boolean operation before subtracting 1 to get the proper ID to use.

See the documentation on tile flipping for more info about the ID’s:
http://doc.mapeditor.org/reference/tmx-map-format/#tile-flipping


#3

Thank you for answering!

I’m fairly new to programming and wanted to ask you how I could implement this in Java? Could you give me an example?


(Nathaniel Nielsen) #4

Sure thing! Though might I first ask how you are loading your maps?


#5

Sorry for replying so slowly, but here is the TileMap file:

package com.neet.TileMap;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.imageio.ImageIO;

import com.neet.Main.GamePanel;


public class TileMap {
	
	// position
	private double x;
	private double y;
	
	// bounds
	private int xmin;
	private int ymin;
	private int xmax;
	private int ymax;
	
	private double tween;
	
	// map
	private int[][] map;
	private int tileSize;
	private int numRows;
	private int numCols;
	private int width;
	private int height;
	
	// tileset
	private BufferedImage tileset;
	private int numTilesAcross;
	private Tile[][] tiles;
	
	// drawing
	private int rowOffset;
	private int colOffset;
	private int numRowsToDraw;
	private int numColsToDraw;
	
	// effects
	private boolean shaking;
	private int intensity;
	
	public TileMap(int tileSize) {
		this.tileSize = tileSize;
		numRowsToDraw = GamePanel.HEIGHT / tileSize + 2;
		numColsToDraw = GamePanel.WIDTH / tileSize + 2;
		tween = 0.07;
	}
	
	public void loadTiles(String s) {
		
		try {

			tileset = ImageIO.read(
				getClass().getResourceAsStream(s)
			);
			numTilesAcross = tileset.getWidth() / tileSize;
			tiles = new Tile[2][numTilesAcross];
			
			BufferedImage subimage;
			for(int col = 0; col < numTilesAcross; col++) {
				subimage = tileset.getSubimage(
							col * tileSize,
							0,
							tileSize,
							tileSize
						);
				tiles[0][col] = new Tile(subimage, Tile.NORMAL);
				subimage = tileset.getSubimage(
							col * tileSize,
							tileSize,
							tileSize,
							tileSize
						);
				tiles[1][col] = new Tile(subimage, Tile.BLOCKED);
			}
			
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	public void loadMap(String s) {
		
		try {
			
			InputStream in = getClass().getResourceAsStream(s);
			BufferedReader br = new BufferedReader(
						new InputStreamReader(in)
					);
			
			numCols = Integer.parseInt(br.readLine());
			numRows = Integer.parseInt(br.readLine());
			map = new int[numRows][numCols];
			width = numCols * tileSize;
			height = numRows * tileSize;
			
			xmin = GamePanel.WIDTH - width;
			xmax = 0;
			ymin = GamePanel.HEIGHT - height;
			ymax = 0;
			
			String delims = "\\s+";
			for(int row = 0; row < numRows; row++) {
				String line = br.readLine();
				String[] tokens = line.split(delims);
				for(int col = 0; col < numCols; col++) {
					map[row][col] = Integer.parseInt(tokens[col]);
				}
			}
			
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	public int getTileSize() { return tileSize; }
	public double getx() { return x; }
	public double gety() { return y; }
	public int getWidth() { return width; }
	public int getHeight() { return height; }
	public int getNumRows() { return numRows; }
	public int getNumCols() { return numCols; }
	
	public int getType(int row, int col) {
		int rc = map[row][col];
		int r = rc / numTilesAcross;
		int c = rc % numTilesAcross;
		return tiles[r][c].getType();
	}
	public boolean isShaking() { return shaking; }
	
	public void setTween(double d) { tween = d; }
	public void setShaking(boolean b, int i) { shaking = b; intensity = i; }
	public void setBounds(int i1, int i2, int i3, int i4) {
		xmin = GamePanel.WIDTH - i1;
		ymin = GamePanel.WIDTH - i2;
		xmax = i3;
		ymax = i4;
	}
	
	public void setPosition(double x, double y) {
		
		this.x += (x - this.x) * tween;
		this.y += (y - this.y) * tween;
		
		fixBounds();
		
		colOffset = (int)-this.x / tileSize;
		rowOffset = (int)-this.y / tileSize;
		
	}
	
	public void fixBounds() {
		if(x < xmin) x = xmin;
		if(y < ymin) y = ymin;
		if(x > xmax) x = xmax;
		if(y > ymax) y = ymax;
	}
	
	public void update() {
		if(shaking) {
			this.x += Math.random() * intensity - intensity / 2;
			this.y += Math.random() * intensity - intensity / 2;
		}
	}
	
	public void draw(Graphics2D g) {
		
		for(int row = rowOffset; row < rowOffset + numRowsToDraw; row++) {
		
			if(row >= numRows) break;
			
			for(int col = colOffset; col < colOffset + numColsToDraw; col++) {
				
				if(col >= numCols) break;
				if(map[row][col] == 0) continue;
				
				int rc = map[row][col];
				int r = rc / numTilesAcross;
				int c = rc % numTilesAcross;
				
				g.drawImage(
					tiles[r][c].getImage(),
					(int)x + col * tileSize,
					(int)y + row * tileSize,
					null
				);
				
			}
			
		}
		
	}
	
}

(Thorbjørn Lindeijer) #6

In libtiled-java, I’ve used a TreeMap<Integer, TileSet> tilesetPerFirstGid since then you can use floorEntry to find the tileset:

In general, I would not recommend looking very closely at this map reader, since it’s slow and confusing due to relying heavily on reflection, but I think this part is quite useful.


#7

Thank you! I’ll try that.


(Nathaniel Nielsen) #8

The main thing I wanted to mention since you say you’re working with Java has to do with this bit of your parser:

Integer.parseInt(tokens[col]) may throw an exception if you decide to use the tile flipping feature in Tiled, since the value that is saved would be an unsigned int, which Java doesn’t exactly support.

From Java’s documentation:

parseInt(String s)
Parses the string argument as a signed decimal integer.

The main difference between signed and unsigned integers is that they use the same number of bits to define a different range of numbers; signed ints being able to represent negative numbers. The exception might occur because the value would appear to be out of range for a signed value.

If you wish to support tile flipping in Java you will want to use this in your parser instead:
map[row][col] = Long.valueOf(tokens[col]).intValue();

The max value of a Long in Java is incidentally the max value of an unsigned int, so you will be able to read in the value correctly. Do note that intValue() may appear negative if a large value was parsed but it would still be correct to use in this instance.

To handle negative id’s you’ll need to add the following constants:

// Bits on the far end of the 32-bit global tile ID are used for tile flags
static final int FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
static final int  FLIPPED_VERTICALLY_FLAG   = 0x40000000;
static final int  FLIPPED_DIAGONALLY_FLAG   = 0x20000000;

And perform the following operation to get the proper ID:

int rc = map[row][col];
// Clear the flags
rc  = rc & ~(FLIPPED_HORIZONTALLY_FLAG |
             FLIPPED_VERTICALLY_FLAG |
             FLIPPED_DIAGONALLY_FLAG);

You may also define a few booleans for readability:

boolean flipHorizontal = (rc & FLIPPED_HORIZONTALLY_FLAG) != 0;
boolean flipVertical = (rc & FLIPPED_VERTICALLY_FLAG) != 0;
boolean flipDiagonal = (rc & FLIPPED_DIAGONALLY_FLAG) != 0;

You’ll still need to get the appropriate tileset info to subtract the first GId, but that should cover some of the quirkiness with Java.

You’re good! I apologize for my much later wall of text, but I do enjoy talking about Java :grin:


(Thorbjørn Lindeijer) #9

Now you made me realize that libtiled-java does not even support these flipping flags yet… :confused:


#10

Thank you for your help but I don’t think I know how to implement the subtract one from tileset ID’s code so I’ll just manually subtract one from the .map files and research until I can figure it out.
But still, thank you very much for helping me! :slight_smile:


#11

It’s okay, thanks for your help, though!


(James) #12

I don’t really want to resurrect an old post, but this small nugget of information about taking 1 off the ID is rather important and should be in the docs.


(Thorbjørn Lindeijer) #13

The documentation doesn’t mention subtracting 1, because that’s not how it works in general. There is a section about the layer data where I tried to explain the global IDs:

Whatever format you choose for your layer data, you will always end up with so called “global tile IDs” (gids). They are global, since they may refer to a tile from any of the tilesets used by the map. In order to find out from which tileset the tile is you need to find the tileset with the highest firstgid that is still lower or equal than the gid. The tilesets are always stored with increasing firstgid s.

Maybe I should mention explicitly, that once you determined which tileset the tile is from, that you need to subtract that tileset’s firstgid value to get the local tile ID in that tileset. This happens in the provided example code.

Only if you use only a single tileset you can just subtract 1, because the first tileset referenced by a map will always have firstgid="1".