Unless you specifically need to use JS (e.g. you’re running a system that doesn’t have a JSON library, or using an engine designed around the Tiled JS format), I think you should use JSON instead, and not worry about the JS format at all. JSON is easier to read and just as easy to parse. You get the same data from both formats (as far as I can tell), but with JSON you get an object you can process when you’d like, whereas with JS you get a function that needs to be executed and which adds the map data to a predetermined list.
The TMX documentation should have most of what you need. The property names and general structure and most of the possible values are the same between TMX and JSON.
Beyond that, where specifically are you getting stuck? The basic approach for just getting data needed to render your tile layers is:
Loop over every tileset in the tilesets
array and store the important data somewhere accessible. For better performance, store tileset data somewhere where you can access it again when you load a different map, as tileset data doesn’t change between maps, only how the tilesets are used changes.
- Store the
firstgid
for each tileset, it’ll be important. This is a map-specific property, so store something like a list of {tilesetReference, firstgid}
, where tilesetReference refers to a Tileset object that has the tileset’s data. For ease, I highly recommend making sure this list is sorted in ascending order by firstgid
, as you’ll be looking up the firstgid
s of tilesets a lot.
- For the simple case, let’s assume you’re using image-based tilesets. From the tileset, the data you need for rendering is
- tileWidth
- tileHeight
- spacing (may be absent or 0)
- margins (may be absent or 0)
- image source (URL of the image).
- image width
- image height
- Once you have all those properties, you can calculate where in the image any given tile is based on its local tile ID. You don’t even need to load the image at this point, you only need it when you’re ready to render.
Now that you have your tileset data ready, you need some important map data:
- mapWidth, the width of the map in tiles
- mapHeight, the height of the map in tiles
- tileWidth, the width of the cells in the map. This map tile size can differ from the tileset tile size in some instances.
- tileHeight, the height of the cells in the map.
Based on these pieces of information, you can figure out where on the map a given tile is just based on its index into the list of tiles.
Loop over every item in the layers
array. For each:
- get either the layer
id
or the index in the array to use as a unique layer ID
- check the
encoding
:
- If it’s “csv”, then you can ignore compression, the data will not be compressed, it’ll just be CSV. Parse the CSV string into a list of numbers. This will be a list of unsigned global tile IDs.
- If it’s “base64”, you’ll need to use a base64 library to decode the data. The result will be some binary data, put it into an
ArrayBuffer
, which is JavaScript’s way of storing binary data. It may or may not be compressed, so look at the compression
field.
- If it’s “gzip”, “zlib”, or “zstd”, use the appropriate library to decompress the data. The result will be an
ArrayBuffer
or ByteArray
(depending on the library you use), which needs to be parsed as an array of unsigned 32-bit integers. You can do this by by making a Uint32Array
from it, var tileIDs = Uint32Array(uncompressedArrayBuffer);
- If it’s empty, the data isn’t compressed, it’s already just an
ArrayBuffer
of ints, so interpret it as 32-bit unsigned integers: var tileIDs = Uint32Array(uncompressedArrayBuffer);
- Whatever the encoding and compression were, you now have an array of global tile IDs.
- When you’re ready to render (or rather, generate your renderable object, you want to prepare it ahead of time instead of doing all this tilework every frame), loop through the list of global tile IDs and process each one:
- A global tile ID, in addition to the actual ID, contains several bit-flags relating to the flipping and rotation of the tile, and you have to extract those first. This is the most confusing part for most people. These are the flags:
- horizontal flip. Extract with
hflip = gid & 0x80000000;
- vertical flip. Extract with
vflip = gid & 0x40000000;
- diagonal flip. Extract with
dflip = gid & 0x20000000;
(for hex maps, this is actually a rotate 60 degrees flag)
- (for hex maps only) rotate 120 degrees flag. Extract with
hexRotate120 = gid & 0x10000000;
- After you’ve saved these flags somewhere (they can just be temporary variables while you set up your renderable), clear them from the global tile ID:
gid = gid & ~(0x80000000 | 0x40000000 | 0x20000000 | 0x10000000)
.
- Your global tile ID now contains only the actual global tile ID. You now need to figure out what tileset it’s for. Iterate through your list of tilesets for this map, and find the tileset with the largest
firstgid
that’s less than or equal to this global tile ID (this is why having the list sorted by firstgid is helpful!). That’ll be the tileset this tile belongs to.
- Subtract the
firstgid
from the global tile ID to get the tileset-local tile ID. As mentioned before, from the local tile ID and the tileset’s data, you can figure out what region of the tileset image it uses. From the tile’s index into the array of tiles and the map’s mapWidth
and tileHeight
and tileWidth
, you can figure out where in the map this tile is located. Load the tileset image if you haven’t already and add this tile to your renderable object.
Once you’ve done this for every tile in every layer, you should have your renderables all ready.
I skipped over a lot of things in this, like layer opacity, rendering offsets, objects, etc, but hopefully this is enough to get you started.