Last updated:
This is a collection of various tips to help you better utilize Tiled. It is not intended for first-time users. Rather, this document collects the advice and answers I often find myself giving on the Tiled Discord and forum, so that it is easier to find and refer to. This is not intended as a replacement for the Tiled documentation, but as a companion to it, and will link to it where appropriate.
In addition, I've noticed that many tutorials focus too much step-by-step processes and that users do not develop an understanding of how Tiled's tools work and are unable to effectively use Tiled in any scenarios that don't closely match what they saw in those tutorials. I hope that my focus on how to approach various tasks in Tiled will help you be a better-rounded Tiled user.
Sometimes it is helpful to use words both in their everyday meanings and in their Tiled-specific meanings. I will capitalize terms when referring to Tiled tools and features, and use all lower-case when using words in their everyday meaning. For example, "Tileset" means a tileset as used in Tiled, such as a TSX file, while "tileset" refers to the broader concept or to tileset images.
If you close a panel or can't find some panel you need, the first place to look is the View
menu. At the top is the Views and Toolbars
submenu, from which you can enable and disable most panels.
The Tile Stamps
panel is closed by default. This is a very useful panel which is barely mentioned in the official Tiled documentation. You can read more about it here.
In Tiled, only maps have orientations - orthogonal, isometric, hexagonal. Tilesets do not have any meaningful concept of orientation, except as a hint to help Tiled render Terrain labels correctly.
A tile is always a rectangular image, no matter the type and style of Tileset. This means that even if you're using an isometric or hexagonal tileset, you can't use a tileset image where the tiles are arranged on an isometric or hexagonal grid, because Tiled will not be able to cut out the individual tiles from that.
The map's orientation determines where these rectangles are drawn, creating the appearance of other orientations. This is how most game engines render non-orthographic tiles, too.
The fact that tiles are fundamentally rectangles regardless of map orientation has implications for Tile Objects: they're always orthographic rectangles, even on non-orthographic maps! This is particularly disorienting on isometric maps, where normal rectangular objects conform to the isometric grid, but Tile Objects do not. If you need to have Objects with world-space coordinates on isometric maps, it is probably better to avoid using Tile Objects and get by with regular Objects. You can set the tile or other graphical information using Custom Properties instead.
For the most part, using hexagonal tiles in Tiled is just like using orthogonal and isometric tiles. However, there are some limitations and bugs that affect hexagonal maps:Hexagonal considerations
Many isometric and hexagonal tilesets feature tiles that have some depth to them. If you set your map's tile size to be the same as the tileset's tile size with such tiles, they will not align correctly. This is because the actual functional surface of these tiles is smaller than the space the tiles take up in the image.
To fix this, the map's Tile Height needs to be set to a smaller value in Map > Map Properties
. The tile height in the Map should be the height of the flat surface of the tile, not including its depth, since the depth should not contribute to tiling a continuous flat area with the tiles. You should use the most basic flat tile in your tileset to determine the Tile Height, do not use tiles that slope or have details that obscure the tile's top surface.
For isometric maps, you can measure the height of the surface of the tile in an image editor, or reduce the Tile Height of the Map in Tiled until the tiling looks correct. For pixel art tiles that don't have anything sticking out their sides, the correct height is almost always 1/2 the width, so that's a good value to start with.
For hexagonal maps, you will also need to set the Tile Side Length correctly to get proper tiling. The most reliable way is to measure in an image editor. If you want to just adjust the sizes in Tiled instead of measuring, start by reducing the Tile Height until the bottom of the tile grid aligns with the bottom edge of the surface of the tile. After that, adjust the Tile Side Length until the tile grid matches the surface of the tile and the tiles tile correctly.
You can use the Up and Down arrow keys on your keyboard to quickly change the value of the properties instead of typing values in. Click the value to edit it, and then you can use the arrow keys. You can also use the small arrows that show up on the field using your mouse.
If your tiles' surface doesn't span the entire width of the tile, you'll also need to adjust the map's Tile Width. This can happen with tilesets that include details that go off the tile.
If your tileset has tiles placed haphazardly, that is, placed such that the top surfaces aren't always in the same place within the tile, you will not be able to get them to align. The only remedy is to adjust the tileset image(s) in an image editor so that the tiles are aligned consistently.
A hidden gem in Tiled is the Tile Stamps panel. It lets you save arrangements of tiles for later reuse, and allows you to paint with random Stamps. This panel is hidden by default, you'll need to enable it in View > Views and Toolbars > Tile Stamps
.
In Tiled, "(Tile) Stamp" doesn't only refer to Stamps saved in the Tile Stamps panel. Any time you use the Stamp Brush, which is the basic drawing tool in Tiled, your brush is a Tile Stamp. Any time you pick one or more tiles from a Tileset to draw with, that's a Tile Stamp. The only things different about the Stamps in the Tile Stamps panel is that they're saved for later use, and that random variations can be selected.
A very useful feature of this panel is the ability to create Variations of Tile Stamps. For example, you can make a tree Stamp and save a bunch of other trees as Variations of that Stamp. Then, when you use that tree Stamp, Tiled will pick a random tree to place. To use this feature, you first need to create a base Stamp. Select the tile or tile arrangement you want to make a stamp out of, and click the Add New Stamp
button. Then, one by one, select your variants and click the Add Variation
button.
Tiled currently has no way to change whether a given Stamp is a base Stamp or a Variation, so be careful when creating stamps. If you accidentally make a base Stamp when you want a Variation or vice versa, you'll need to delete and re-add it.
After you've created your Stamp with Variations, any time you paint with the base Stamp, a random Stamp will be selected from among the base Stamp and its Variations. If you click the arrow to the left of the Stamp in the Tile Stamps panel, you can expand or collapse the Tile Stamp. When expanded, you can view and edit the Variations. One of the properties you can edit is the Probability, which lets you control how often a particular stamp shows up relative to the others. Probability is only meaningful for Variations. The probability listed for the base Stamp is just the sum of the probabilities of the variations and isn't used for anything.
If you select a Variation to draw with instead of the base Stamp, only that Variation will be used. This means you can also use Variations as a way to group Stamps together, rather than just for randomness.
When using Variations, you'll usually want the Stamp Brush's random mode to be off, so that the entire Stamp is drawn, rather than a random tile from it.
Some of the stock tilesets you'll find online, both free and paid, will be designed for RPG Maker autotiling. Such tilesets are stored in a compressed format, designed to be broken up into smaller sub-tiles and reassembled into full-size tiles at runtime. Tiled cannot use such tilesets directly effectively.
In theory, you could use such tilesets in Tiled by loading them in with half the tile size they're intended for, but this will make it troublesome to select the exact tiles you need, and they won't play very nicely with Terrains. Instead, you should expand the tilesets by building full tilesets out of their subtiles. These are some tools available to automate this:
I recommend the last one (full disclosure: I wrote it), as it runs directly in Tiled (1.8+), and outputs a map rather than an image. You can export this map as an image to use for a tileset, or you can save the map and use it directly as the source "image" of a new tileset. If you do the latter, you'll have a metatileset that still uses the original, much smaller, source image. The disadvantage of using a metatileset is that not all parsers/engines support them.
If you'd prefer to use an image, there's still one advantage to using a metatileset at least temporarily: You can use a script such as this one to import Terrains from the original RPG Maker tileset to the expanded one. This allows you to label the Terrains only on the original tileset, which is usually much less work. After you're done, you can change the tileset's source to the exported image instead of the map, and keep the imported Terrains.
Terrains are simple to use once you set them up, but if you don't understand how the feature works, you may have difficulty correctly labelling your tiles if the tileset is complex or arranged in an unusual way. There's really just one thing to understand about Terrains, everything else flows from it: the labels tell Tiled how the tiles are intended to connect rather than what's on them, if the labels on the adjacent sides of two tiles match, they're allowed to placed next to each other, otherwise they are not. This is different from how some other level editors approach this task, but has the benefit of being very flexible.
This concept of matching labels is applied in all four directions, the Terrain tools will try to find an arrangement of tiles such that all the labels on adjacent sides match perfectly. It is from these small relationships that larger coherent terrain shapes are built up.
Before you can label your tileset with Terrains, you need to understand how the tiles should be used, how they should connect to each other. If you've just downloaded a new tileset, take the time to play around with it by manually placing tiles before you start labelling it with Terrains. Understanding how your tileset works will make labelling it much easier. Consider not only which arrangements of tiles you're likely to use, but also how you may split your map across layers. For example, many RPG-style tilesets have plateaus where the top and sides of the plateau has transparency, which means plateaus should be drawn on a layer above the base terrain, and those Terrains should therefore transition to empty rather than to the ground Terrains.
In the interest of performance, Tiled does not do an exhaustive search for a valid arrangement of tiles. So, Tiled will sometimes make mistakes and leave you with an invalid arrangement if you're using incomplete Terrains. Because of this, it's best to make your Terrains as simple and complete as possible. It's often better to handle the complex transitions with Automapping that runs after you paint with Terrains than to try to do everything with Terrains.
Not every tileset is well-suited to being used with Terrains. Some tilesets have complex, multi-tile transitions that can't be easily represented with Terrains, some rely on sharp boundaries and only have a small number of transitions, some rely on each tile being used in several contexts. Such tilesets are better used with Automapping than Terrains.
Some tilesets can be used with Terrains, but have only a small subset of transitions that they support, which will lead to broken results if you try to draw unsupported situations with them. It's important to know your tileset's limitations.
And lastly, some tilesets are so irregular that neither Automapping nor Terrains are a good fit, their tiles are best placed manually.
To avoid accidentally mislabelling some tiles, look through your tileset and figure out which tiles are part of which terrains, before you start labelling. Sometimes things that look different may actually be part of the same terrain. For example, a tileset may look like it has water, sand, and grass terrains, but the sand may just be decoration at all water-grass edges, so you wouldn't actually need a separate sand terrain, just water and grass. As a starting point, look for those tiles are filled with some terrain (e.g. all-water, all-grass tiles), and see if there are tiles that could serve as transitions between those tiles and other filled tiles.
Some Tilesets have multiple independent sets of terrain in them, that is, groups of terrains that connect with other terrains in their group, but not with terrains in other groups. For example, a tileset may have some interior wall and floor tiles that can be used as Terrains, but which have no way to connect to the exterior Terrains. I recommend having these as separate Terrain Sets, so that you don't have to look through the entire list of Terrains every time you want to find the one you want.
When you create a new Terrain Set, you will need to choose a Terrain Set type. If you don't know what type of terrain you have, try to build the smallest shapes of each terrain that you can with the tiles, not counting 1x1 single-tile "islands". If it's a filled 2x2 shape connected at the corners, it's a Corner-based terrain. If it's a 2x1 or 1x2 line, it's an Edge-based terrain. If you can make both, it's a Mixed terrain.
Because most tilesets are incomplete, you may find yourself unable to make these shapes in every combination of terrains, but only some of them. In particular, with Mixed tilesets, it's very common that you can make the two-tile shapes with one terrain, but not with the terrain around it. The island tileset above, for example, can make two-tile islands but not two-tile lakes. If you suspect you may be dealing with an incomplete Mixed tileset (i.e. you can make four-tile corner-linked shapes, but have way more tiles than would be needed for a corner-based terrain), try making the two-tile shapes in both configurations - terrain A inside and terrain B outside, and vice versa. If at least one of them works, you've definitely got a Mixed Terrain Set.
It's common for a single tileset to contain multiple sets of terrains with different types. For example, top-down tilesets usually have Corner-based ground terrain, but may include Edge-based details like roads and fences. For this reason, you should check the type for all the different terrains you want to use.
The types and labels for each Terrain Set are independent, so you can set up the labels in each Terrain Set in whatever way is most useful for that set, the label for a particular tile can be different in each Terrain Set.
Once you've created your Terrain Set, you can start labelling. Place each terrain colour at each corner and/or edge (depending on your terrain type) where that terrain occurs. The Terrain documentation has more information on the UI for labelling and using Terrains.
If you're working with an isometric tileset, you can set the Orientation in the Tileset Properties to "Isometric" and set the grid size to match your tiles' visuals, so that the Terrain labels more closely match the art.
My usual advice for labelling Terrains is to put each label colour on those corners/edges of each tile where that terrain or material is present. For many tilesets, especially top-down ones, the labels will match the art well.
Choose your Terrains' colours so that they resemble or evoke the terrain they represent, such as blue for water and green for grass, like in the example above. This can make labelling less confusing. You can customize the colour for your Terrains by right-clicking the Terrain name in the list of Terrains and choosing "Pick Custom Color".
Avoid using colours that exactly match the tile art, choose more saturated colours instead, so that they stand out from the art.
However, it can be harder to see where each terrain goes if the art on the tiles has different proportions from the Terrain labels in Tiled.
The key thing to keep in mind is that when you label Terrains, what you're doing is telling Tiled how the tiles should connect to one another. It doesn't matter what the labels look like, what's important is that the edges/corners of tiles that can be placed together have matching labels. In the above example, the outermost parts of the tiles that transition to empty space are left empty (unlabelled), and the rest is filled with the ground Terrain.
When labelling Terrains, take care to stay focused on the individual tiles and how they may connect with other tiles. Do not fall into the trap of looking at the overall shapes your labels make across neighbouring tiles. While there are some common arrangements of tiles in tilesets, such as the 3x3 block of corner-based terrains, tiles can be arranged in any way and you can't rely on their arrangement. Pay attention to the tiles instead.
Some tilesets include 1x1 "island" tiles of some of their terrains. You can see two such tiles in the example above, the two unlabelled tiles. The top right one is ground on all sides and empty in the middle, the bottom left one is empty on all sides and ground in the middle. Since Tiled has no way to mark the middles of tiles with Terrains, these tiles are functionally identical to the all-ground and empty tile, respectively. Such tiles should be placed by hand.
However, it is a good idea to label them where possible, so that if you do place them manually and then edit the surrounding tiles with the Terrain tools, the tools will know how to connect other tiles to them. In order to prevent those tiles from showing up automatically as random variants of the regular tiles with those same labels, set their Probability to 0 in the Tile Properties.
When labelling a Mixed Terrain Set, it's common that some of the Terrains are only really important at Corners, while others are only important at Edges. In these cases, only label those! It's perfectly fine to leave some Corners or Edges unlabelled if it doesn't help you specify how the tile should be used. Label only what's important, don't label anything that doesn't need a label.
When Tiled detects that a given Terrain in a Mixed Terrain Set is only used at the Corners or the Edges, it'll snap to those parts of the tiles when you paint with that Terrain, making the Mixed Terrain Set easier to use.
Most tilesets out there are incomplete. Even those featuring just one set of corner-based terrains, something that normally only requires 15 or 16 tiles, are usually missing tiles. Tiled can handle incomplete Terrains to some degree, and most common types of incomplete tilesets will work well. However, in some cases, Tiled will have no idea what to do, and will either not let you draw at all, or will draw an explosion of garbage. If you find yourself facing these two results too frequently, consider expanding your tileset, those missing tiles may actually be important for your maps.
For Corner Terrain Sets, it's very common to leave out the two tiles where opposite corners match. This means that you can't draw very tight, snaking shapes with those tiles, and should instead draw your shapes spaced out. If you run into issues using Corner Terrains, check if you're missing these two tiles, adding them can help.
Edge Terrain Sets for things like fences often only include those tiles where only one or two of the edges are the "fence" and the rest are the surrounding terrain. This means that the "fences" can't branch, as that would require tiles where three of the edges are "fence". If only tiles where two of the four edges are "fence" are included, the fence can only make closed loops, it has no way to terminate to make an opening.
A complete Mixed Terrain Set with two Terrains would require 256 tiles. Unsurprisingly, it's very rare to find such a tileset, as that's a lot of art to produce, and most of those tiles aren't that useful. Instead, it's far more common to use the 47-tile "Blob" subset. This style of tileset allows for a large variety of terrain with only a fraction of the tiles.
One very common mistake involving deliberately incomplete tilesets is to try to draw on an empty layer with a Terrain that has no way to transition to empty tiles. Tiled will not let you draw anything at all since there's nothing it can draw, making the Terrain Brush appear to not work. The solution is to fill your layer with the base tile for one of the Terrains before you start drawing with the Terrain Brush, so that there's something valid for your desired Terrain to transition to.
Tilesets not designed with Terrains/automapping in mind often require incomplete Terrains to properly label, and the incomplete terrains often give Tiled trouble, sometimes to the point of barely being useable. Automapping can be a better option in these cases. If you are an artist designing tilesets, I highly recommend getting to know the Terrain tools in Tiled and making your tilesets work with them. Basic two-colour Terrains in Tiled will also work well with Unity and Godot's autotiling tools.
Not every tileset fits nicely with Tiled's concept of Terrains. You can't always just add labels that tell Tiled, "here's Terrain A, here's Terrain B", but that doesn't mean you can't make those tiles work with Terrains. Sometimes you just have to get creative.
A not uncommon scenario is when two sets of tiles have corresponding edges, but no actual transition tiles.
If you label the edges of one of these as being the other terrain, then it won't connect to the correct Tiles of the other terrain. Instead, you need to tell Tiled that the edges of one link up with the edges of another. And the mechanism Terrains have to do that is... another Terrain.
If you make a terrain but Tiled won't place any tiles when you try to use the Terrain Brush, the most likely culprit is that your terrain has no way to transition to whatever is already on the layer. This commonly occurs when painting on an empty layer with a terrain that has no transitions to empty. Try filling the layer with a solid tile from the terrain set you want to use, and then painting with that.
Certain scenarios or ones comparable to them come up very frequently across many projects. This section includes some tips for dealing with them efficiently in Tiled.
When placing trees and other tall entities in a 3/4-like view, it's common that the player and NPCs should be able to overlap them when walking in front of them, while being overlapped when walking behind them. If trees are placed on a layer as a bunch of small tiles, it can be difficult to communicate to the engine that some of those tiles should be in front of the player, while some should be behind them.
It's also common that trees and such should be able to overlap other trees that are behind them. However, if your trees are made out of plain tiles, trying to place them in a single layer doesn't work correctly, as one tree's tiles will overwrite the tiles of the trees that already overlap those tile cells.
Both of these problems share the same solutions. There are two common approaches, each with its own pros and cons:
You can sort your tree tiles into two layers: a foreground layer that contains the tiles that are always on top of the player, and a background layer that contains the tiles that are always behind the player.
Pros:
Cons:
You create a tileset where each tree is a single tile, so it's impossible for a part of the tree to be overwritten by something else, and so that the tree can be z-sorted as a single entity. In Tiled, you can use such oversized tiles on a Tile Layer, in which case they'll stick out of their cells, or you can place them as Tile Objects on an Object Layer. In either case, in-engine, you'll need to z-sort the trees at runtime to know what order to render them and the other entities in.
Pros:
Cons:
If you move a Tiled Map or Tileset file relative to any files it uses, such as tilesets or images, the references to those files will become broken. When you open that Map or Tileset in Tiled, Tiled will be unable to find the files and will prompt you for them. Until you fix those references, Tiled will not be able to display any tiles, objects, or image layers that use them.
In the list of missing files, you can select multiple entries at once and select the directory all those files are in. This can save you lots of time if you have a lot of missing files that are all in the same place.
You can avoid breaking file references by always moving Maps, Tilesets, and the files they need together. This is much easier if you practise good file discipline from the start:
In addition to being a good organizational tool even if your files aren't part of some greater project, creating a project directory like this will also make it easier to use Tiled's Project feature and often avoids issues when importing your Tiled files into game engines.
If you're using the Unity engine and using SuperTiled2Unity to import Tiled Maps, make your Tiled project directory inside of your Unity project's Assets directory. This way, you will not need to move or copy files at all, so you'll not risk breaking any references. As a bonus, ST2U will be able to automatically detect changes to your files and reimport them.
If you're parsing Tiled files yourself rather than using an existing library or exporting in an engine-specific format, you may run into some of these issues when trying to read the Tile Layer data. Fortunately, they all have relatively simple solutions.
Tiled supports several data formats for the list of tiles that make up each Tile Layer. It's quite likely that you'll find layer data that looks something like this:
eJztzwENwCAQBEEQVKQhDWnVUBPNPYEZA5ttDQAAAAAAgD+Ny/oz3Kvur3Cvuu/37P4b7lX3Rw8Hi/tPNrddHyDlA1RLB+E=
This can be rather intimidating since it doesn't look like the list of tile IDs you might be expecting, but don't worry. The map contains information to help you parse this data, and Tiled has options for formats that you might find easier to understand.
The Tile Layer Format can be changed in Tiled in the Map Properties, this determines how the layer data is stored in the map files. The following options are available:
In TMJ (Tiled Map JSON) maps, the "CSV" format actually outputs a native JSON array rather than a CSV string. This means rather than parsing the JSON yourself, you may be able to use your JSON library's built in array parsing.
If you look at the data field in the layer, it should have encoding
and (optionally) compression
properties. The examples here will use the TMX format, but this is all pretty similar with JSON format and any other export formats that respect the Tile Layer Format property in your maps. If the encoding
is "csv", then you're dealing with a CSV string, which should be fairly easy to figure out. If the encoding
is "base64", then read on.
The Base64 formats are should all be dealt with roughly the same way:
compression
is set, decompress. The compression options are "zlib", "gzip", and "zstd", and you'll need to run the appropriate decompression algorithm based on the value of the property. If the compression
property isn't set, then skip this step, because the data wasn't compressed. Again, the exact way to do this will depend on the language and libraries you're using, but zlib and gzip libraries are widely available. Generally, they'll take a byte array or a string as an input, and output a decompressed byte array or string.This might sound complicated, but once you have the decompression libraries you need, the actual code should be fairly simple. Here's some C++ that's very similar to what I do in my engine, using zlibcomplete for decompressing zlib and gzip:
//get the layer data from the file: std::string data = dataNode->value(); //Prepare a container for the layer data: unsigned int* tileGIDs = new unsigned int[mapWidth*mapHeight]; if(std::strcmp(encoding, "base64") == 0) { data = base64_decode(data); if(std::strcmp(compression, "zlib") == 0) { zlibcomplete::ZLibDecompressor decompressor; data = decompressor.decompress(data); } else if(std::strcmp(compression, "gzip") == 0) { zlibcomplete::GZipDecompressor decompressor; data = decompressor.decompress(data); } else if(std::strcmp(compression, "zstd") == 0) { //unsupported compression //report an error, clean up, abort loading } //Copy the bytes into the unsigned int array: memcpy(tileGIDs, data.data(), data.size()); } else if(std::strcmp(encoding, "csv") == 0) { //parse the data string as CSV, into the tileGIDs array: parseCSV(data, tileGIDs); }
If you encounter tile IDs in the data that are much larger than the maximum tile ID you expect, these are probably flipped/rotated tiles. Tiled uses the most significant four bits for flip and rotation flags. Before you can get at the tile ID, you should read these flags and store their state so that you can apply these transformations to the tile when you render, and then you need to clear the flags, so that only the tile ID remains.
The Tiled docs have more information on these flags and parsing them, including a code example.
If you're rendering your tiles successfully but getting the wrong tile from what you expect, you're probably neglecting the firstgid
of your tileset. A Tiled Map can have tiles from multiple Tilesets, and there needs to be some way to tell which Tileset a tile is from. Just using the tile ID directly wouldn't work, since each Tileset has its own, independent tile IDs, typically starting from 0. Instead of using these conflicting IDs, every tile in a map uses a global ID, or a gid for short, so that every tile can have a unique identifier even if there are multiple Tilesets in the map.
The Tiled documentation has a section on dealing with firstgid
s.
Maps that have only one Tileset have a firstgid
of 1 for that Tileset. If your parser only supports single-Tileset Maps, you can generally assume that Tileset will always have firstgid
1.
Although firstgid
s are usually assigned based on the previous Tileset's tile count, this is not a guarantee and should not be relied upon. Tilesets' sizes may change after the map is saved, and Tiled could easily start basing the firstgid
on the tiles that are actually used within the map instead. When dealing with multiple Tilesets, always read the firstgid
from the Map instead of guessing.
Most engines support tilemaps natively, so if you're using an existing engine, you probably don't need to care about rendering them, and only need to worry about getting the Tiled map data into your engine. However, if you're writing your own tilemap renderer or otherwise need some help in figuring out how tiles in Tiled connect to drawable entities in-engine, this section may be of use.
Tiled supports two kinds of Tilesets: Image Collections, and Based on Tileset Image.
In Image Collections, every tile is its own separate image, so to render a given tile, you'd look up its image and render it. As of Tiled 1.9, these Image Collection tiles have x
, y
, width
, and height
properties which determine where exactly in the image the tiles are, so you should draw the subrectangle of each tile's image that's defined by these properties.
Tilesets that are Based on Tileset Image are all subrectangles of the Tileset's source image, but their locations are not explicitly defined anywhere. Instead, they can be simply calculated from the tile ID like this:
x = tileID % columnCount; //in tiles x = x * (tileWidth + spacing) + margin; //now in pixels y = floor(tileID / columnCount); //in tiles y = y * (tileHeight + spacing) + margin //now in pixels
At the end of this, x and y contain the pixel coordinate of the top left pixel of your tile. To get the other three corners, add tileWidth to x and tileHeight to y as needed.
The column count is the width of the Tileset in tiles. It's included as a columns
field on the Tileset in most versions of Tiled, and can be calculated for very old tilesets, which don't have it, from the image width, tile width, margin, and spacing:
columnCount = floor( (imageWidth - margin + spacing) / (tileWidth + spacing) );
Tiled currently has a single spacing
value and a single margin
value and these unified values are used in the examples above, but it's possible that one day, it will be possible to set these values independently for x and y. I recommend using separate x and y variables for each in your calculations, and just assigning the current unified value to both for now, so that in the future, you only have to update your code that reads these values, and not your calculations.
While it is outside the scope of this document to describe 2D video game rendering, this section will cover where each grid cell is positioned for the different map orientations in Tiled, as this is not always trivial, and few engines natively support non-orthogonal orientations, and even those that support orthogonal orientations don't always support drawing the tiles in all the ways Tiled does.
In general, you should render layers from bottom to top, i.e. in the order that the layers appear in the Tiled map file, and each layer is largely independent. Each layer in Tiled can have its own offsetx
and offsety
, this means the layer should be drawn that many pixels right and down from its default location; in most engines you can simply subtract the offset from the layer's origin. If that's not an option, you'll need to add the offset to the layer's render position every time you draw it.
For simplicity, this section speaks of "drawing" tiles in various orders and locations. You should not actually render tiles one by one, that's very slow! Instead, add them to a single drawable entity (vertex array, sprite batch, etc) in that order and at that location, and draw that entity all at once.
Tiled supports four map orientations: orthogonal, isometric, isometric (staggered), and hexagonal. The actual tile textures are always rectangles, and the rendered layer can be thought of as a screenspace rectangle, so all these orientations change is where each cell is. The cell is the slot where a tile can be, the cells the spaces between the grid lines that you can see in Tiled. Cells and tiles aren't the same thing. A cell may be empty, which means no tile should be drawn there, and more importantly, tiles may stick out of their cells, may not fill the entirety of the cell, and may be offset from their cells - sometimes all at the same time. The rest of this section will discuss cell positions, but to figure out where to draw the tile relative to the cell, you'll need to do some additional work, in this order:
1.0
, but tilerendersize
on the Tileset may be set to grid
, which means the tiles should be scaled to fit the grid size. In that case, the exact scaling depends on the fillmode
. If it's stretch
(default), then divide the cell width by the tile width to obtain the x scale factor, and the cell height by the tile height to obtain the y scale factor. If it's preserve-aspect-fit
, then do the same, but then use the smaller value of the two for scaling both the width and height of the tile. The tile's render size is its base size multiplied by the calculated scale factor. Since tiles can differ in size and shape, this calculation has to be done for each tile, though you can cache the results since within a map, each cell is the same size.In all Maps, the cells are the map's tileWidth
in width and the map's tileHeight
in height. The different orientations only change where each cell is, not the cells' size.
To match Tiled, tiles should be drawn with bottom left alignment, that is, the bottom left corner of the tile's bounding box should be in the bottom left corner of the cell's bounding box. If the tile is drawn larger than the cell, that means it'll stick out at the top and left side, and if the tile is drawn smaller than the cell, it'll leave space above and to the right. Since most rendering happens relative the top left, that means you'll need to subtract the tile's render height from the bottom left corner position, this'll give you the location of the top left corner of the tile for that cell. This has to be done for each tile independently, as tiles may have differing sizes.
If the fillmode
is preserve-aspect-fit
, the tile should be centered within its bounding box. Add half the difference between the cell size and tile's scaled size to the render position. For simplicity, you can apply this to stretch
Tilesets too - the difference will just always be 0, 0
and not change the render position.
tileoffset
property, this should be scaled by the render scale and then added to the tile's render position.This process is the same regardless of map orientation, so in the orientation-specific sections below, I will focus on calculating the bottom left origin for each tile. From this, you can calculate the position at which to render the tile following the process above.
If this looks intimidating, then start by ignoring the tilerendersize
- assume tiles should be rendered at their native size. This was how Tiled worked until 1.9, and it's how most tilesets are designed to work. You can add the scaling later if you need it.
In the orientation-specific sections below, x
and y
are always the cell's coordinates, i.e. in map-space, and are assumed to be integers. gridWidth
and gridHeight
are the map's tile size, which may be different from the size of the tiles. These pseudocode examples are based on the code Tiled internally uses to translate map-space coordinates to screenspace coordinates.
Orthogonal Maps are the simplest. The four sides of a cell's bounding box are simply:
left = x * gridWidth; right = left + gridWidth; top = y * gridHeight; bottom = top + gridHeight;
The bounding box of an orthogonal cell is the cell itself. This is not true for the other orientations.
For orthogonal maps only, Tiled supports the Tile Render Order property, which determines the order in which tiles are drawn, affecting how tiles that stick out of their cells overlap. All this really affects is the direction you iterate x and y in when drawing the tiles, so that tiles drawn first are on the bottom, overlapped by tiles drawn later. The render orders starting with "Right" go from left to right, meaning x starts at 0
and increases. Those starting with "Left" are the opposite, they start at mapWidth - 1
and decrease. Render orders ending in "Down" go from top to bottom, meaning y starts at 0
and increases, while those ending in "Up" start at the bottom, so y starts at mapHeight -1
and decreases.
Isometric Maps are diamond shapes with 0,0 at the top corner, x increases towards the lower right, y increases towards the lower left. You can calculate the bounding rect of the isometric cell thus:
originX = mapHeight * gridWidth / 2;
xPixel = (x - y) * gridWidth / 2 + originX;
yPixel = (x + y) * gridHeight / 2;
//xPixel and yPixel are at the upper corner of the cell,
//i.e. the middle of the top edge of the bounding box.
left = xPixel - gridWidth / 2;
right = xPixel + gridWidth / 2;
top = yPixel;
bottom = yPixel + gridHeight;
originX
is the x coordinate of the origin point in the map. Every row of tiles adds a tile's worth of width to the map's screenspace width, so the map's width is mapHeight*gridWidth
, and originX
is half that since the map's origin is in the horizontal middle of the map in screenspace.
The render order for isometric maps is back to front. Since these maps are skewed, this means you can't just iterate each row, the ordering is more complex. A simple method to iterate the cells this way is to iterate the sum of x and y:
for(sum = 0; sum < mapWidth + mapHeight - 2; ++sum) {
for(x = 0; x < sum && x < mapWidth; ++x) {
y = sum - x;
if(y < mapHeight)
//render the tile at x, y
}
}
This method is convenient if you're building a single drawable entity for the whole layer. If you need to limit which parts you draw based on some screenspace rectangle, such as when using software rendering, then it may be more beneficial to iterate in screenspace, stepping by gridWidth
in x and gridHeight / 2
in y, convert each location to map space, and if it's the coordinates are within the map, draw that cell. Screenspace to map-space conversions are outside the scope of this text, but you can take a look at IsometricRenderer::drawTileLayer
and IsometricRenderer::screenToTileCoords
in Tiled's source code for inspiration.
Staggered maps are roughly rectangular in shape, but have isometric or hexagonal cells. Isometric staggered maps are practically identical to hexagonal staggered maps, except that their sideLength
is always 0
. The stagger axis has a large effect on how coordinates are calculated, and the stagger index also comes into play. The calculations for these maps are more complex because of the staggering, some cells are offset relative to their neighbours.
//Some useful functions and values: bool shouldStaggerX(int x) { if(staggerX) { if(x % 2 == 0) //x is even return staggerEven; else return !staggerEven; } return false; } bool shouldStaggerY(int y) { if(!staggerX) { //if staggerY if(y % 2 == 0) //y is even return staggerEven; else return !staggerEven; } return false; } columnWidth = gridWidth / 2; rowHeight = gridHeight / 2; if(staggerX) columnWidth += sidelength / 2; else //staggerY rowHeight += sideLength / 2; //Finally, the actual cell calculations: if(staggerX) { xPixel = x * columnWidth; yPixel = y * (gridHeight + sideLength); if( shouldStaggerX(x) ) yPixel += rowHeight; } else { //staggerY xPixel = x * (gridWidth + sideLength); if( shouldStaggerY(y) ) xPixel += columnWidth; yPixel = y * rowHeight; } left = xPixel; right = xPixel + gridWidth; top = yPixel; bottom = yPixel + gridHeight;
staggerX
is a boolean indicating whether the map's stagger axis is X (true
) or Y (false
), and staggerEven
is a boolean that determines whether the stagger index is even (true
) or odd (false
). columnWidth
and rowHeight
differ from gridWidth
and gridHeight
because the rows or columns are staggered, meaning they overlap. Fortunately, these two values are consistent across the entire map, so you only need to compute them once. The shouldStaggerX
and shouldStaggerY
functions check whether the current tile should be staggered, their output depends on the map's stagger index and the tile's position. In languages where booleans can be coerced into integers, these two functions can be rewritten as one-liners that avoid branching logic:
bool shouldStaggerX(int x) { return staggerX && (x & 1) ^ staggerEven; } bool shouldStaggerY(int y) { return !staggerX && (y & 1) ^ staggerEven; }
In this example code, I split the staggerX and staggerY functions. You can inline the stagger checks too, but it's easier to read when they're separated like this.
Like isometric maps, staggered maps are rendered back to front, and the exact way to achieve that depends on the stagger axis. If it's Y, then just iterate by rows:
if(!staggerX) {
for(y = 0; y < mapHeight; ++y) {
for(x = 0; x < mapWidth; ++x) {
//render the tile at x, y
}
}
}
If the stagger axis is X, then for each row, you'll need to first draw every odd tile (if stagger axis is even) or every even tile (if stagged axis is odd), and then draw the remaining tiles (even or odd):
else { for(y = 0; y < mapHeight; ++y) { startX = 0; if(staggerEven) startX = 1; for(x = startX; x < mapWidth; x += 2) { //render the tile at x, y } if(startX == 0) startX = 1; else startX = 0; for(x = startX; x < mapWidth; x += 2) { //render the tile at x, y } } }
Tiled supports scripting via JavaScript and via Python, but the Python API is out of date and it's not recommended to use it. This section will deal only with the JavaScript API. Details on the JavaScript scripting system can be found in the Tiled scripting documentation. This section will attempt to fill some gaps and inconveniences of the official docs, which unfortunately have a poor search feature, making it difficult to find things if you don't already know what you're looking for. As such, there will be few details in this section, instead there will there will be many links to the API documentation.
Scripts are typically run via the Tiled Editor GUI, but they can also be run via the CLI: --export-map
and --export-tileset
can use scripted Map and Tileset formats, and you can execute arbitrary scripts with --evaluate <scriptFile> [args]
. The latter allows you to pass additional parameters to the script as well, which you can access in the script via tiled.scriptArguments
.
When running scripts via CLI, none of the GUI features are available. This includes most of the functionality in the Tiled GUI section below, methods like tiled.trigger()
and tiled.open()
, and, perhaps surprisingly, TileMap.automap()
, as Automapping is part of the Tiled Editor GUI and not part of the core Tiled library.
If you want to write scripts that work both via CLI and GUI, you should avoid relying on the GUI-specific features and tiled.scriptArguments
. If your script needs the current document, the way to do this will vary: if the user is running the GUI, then tiled.activeAsset
will work, and if that's null
, you can check tiled.scriptArguments
for a path, which you can then read.
When running scripts in the Tiled GUI, they can access many parts of the GUI, so your scripts can respond to the user's current brush, chosen tileset, current document tab, etc. These parts of the API are not available to scripts running via the CLI.
You can get the current active document via tiled.activeAsset
, and the list of all open documents via tiled.openAssets
.
Tiled's GUI consists of two major parts, each with their own selection of panels: the Map Editor, which is shown when the active document is a Map, and the Tileset Editor, which is shown when the active document is a Tileset. Understanding this split is important if you want to write scripts that interact with the GUI. For example, the Terrain chosen in the Map Editor's Terrain Sets panel is used for painting on Maps and has nothing to do with the Terrain chosen in the Tileset Editor's Terrain Sets panel, which is used for assigning Terrains to Tiles.
Scripts can interact with the following Map Editor features:
tiled.mapEditor.currentBrush
)tiled.mapEditor.currentMapView
), which lets you change the zoom and focus of the view for the current maptiled.mapEditor.tilesetsView
), which lets you change which tileset is displayed, and which tiles in it are selectedtiled.mapEditor.currentWangSet
) and current Terrain (tiled.mapEditor.currentWangColorIndex
)The Layers and Objects panels aren't accessible via scripts directly, but they reflect the state of the current Map, so you can interact with them through the Map document: TileMap.layers, ObjectGroup.objects (Objects are accessed through their Object Layers) TileMap.selectedLayers, TileMap.selectedObjects.
Scripts can interact with the following Tileset Editor features:
tiled.mapEditor.currentWangSet
) and current Terrain (tiled.mapEditor.currentWangColorIndex
)tiled.tilesetEditor.collisionEditor
), where you can control the selected collision objects and modify the view similar to the map view. To actually modify the collision objects of a Tile, you should go through the Tile itself.There are a number of quirks to the scripting API - some due to bugs, some due to the API functions originally being written for purposes other than scripting, some due to Qt/Tiled limitations. This is not an exhaustive list.
When editing Tile Layers with setTile()
, the apply()
function currently takes the user's selection into account, so any modifications you make that are outside the user's selection will not be applied. You can get around this by clearing the selection, e.g. map.selectedArea = Qt.rect(0,0,0,0)
. You should do this any time that you're modifying a document the user may have previously interacted with, unless you specifically want to only modify tiles within their selection (in which case, you can make your script perform better by only doing the work within the selection in the first place). This is issue #3482.
If you clear the user's selection to avoid this issue, consider saving it to a variable first, and then restoring it.
Although it is a property, Tileset.tiles
creates JavaScript-side Tile objects every time you access it, making it rather slow. Instead of accessing it repeatedly, save it to a local variable, and then do work with that variable, as much as you can.
Resizing a layer via resize()
, whether directly or via resizing the map it's in, causes the layer to be replaced with a clone, invalidating any existing references you may have to that layer. This means that you should avoid resizing any layers that aren't part of a map (or you won't be able to recover them!), and if you need to compile a list of layer references for a map, you will want to do it after any resizing. This is tracked as issue #3480. You can resize by changing a layer's or map's width
and height
properties without issue.
There is currently no way to open a Tiled document "silently" - without displaying it to the user. This becomes an issue when you need to open Tilesets to add to a TileMap in a custom map format, for example. It's possible to read the contents of a Map or Tileset file with the appropriate format's read()
method, but this creates a copy disconnected from the original file, and using a Tileset loaded this way in a Map would create an embedded Tileset. There might eventually be something like tiled.load() that will open a document without displaying it to the user.
tiled.open()
is the correct way to go in this case, at least for now. You can tiled.close()
the document after you're done with it, though you'll want to avoid closing any documents that were already opened by the user. You can achieve this by first checking tiled.openAssets
for the document you want, and only doing open()
and close()
if you don't find it there.
All Tiled scripts share the same global context. Any persistent variables you declare in one script, can be accessed in any code that runs later, even from other scripts. You should avoid creating more global variables than you need, and you should be careful to name them specifically enough that they're not likely to collide with the variables of another script.
This isn't a quirk, but rather a natural consequence of having JavaScript-side proxy objects for objects on the C++ side: you can assign any property on the JS objects, but only properties that are valid for the C++ objects will make it to the C++ side for use and display in Tiled. You can, however, take advantage of this to conveniently store temporary data about Tiled objects. For example, if you register an Action in Tiled, you can store its configuration on the Action object in JS, without interfering with the action's functionality, without filling up the global space with a bunch of variables.
If you want to open a Map or Tileset in Tiled that isn't already supported, you should use tiled.registerMapFormat()
and tiled.registerTilesetFormat()
, which will allow many parts of Tiled and other scripts to work with this format. Formats let you define read()
and write()
methods, but it's fine to only define one of them if you don't need the other. If your format only supports write()
, you'll only be able to save to it via Export As, not Save As.
If you need to write a Map or Tileset to a file in a format Tiled supports (whether by default, or with a custom format), you can use the appropriate MapFormat's or TilesetFormat's write()
method. You can get the Format for your desired file type with tiled.mapFormat(shortname)
and tiled.tilesetFormat(shortname)
. You can also get lists of each type of format via tiled.mapFormats
and tiled.tilesetFormats
.
If you need to read a Map or Tileset file in a format that Tiled supports, you can use the MapFormat and TilesetFormat as above, but using read()
instead of write()
. This will create a new TileMap or Tileset object for you to use, but will not open it in the GUI editor, so it will not have Undo states or any other GUI-related features available, and it'll be a brand new Asset disconnected from its original file, with no fileName
. You can open it as a document in the GUI by assigning it to tiled.activeAsset
, but it'll still be disconnected from its original file location. If you want to open a Map or Tileset in the GUI, use tiled.open()
instead, which will open it as the active document in Tiled. If you don't want that, you can either wait for issue #3517 to be resolved and a tiled.load()
method to be added, or you can save a reference to tiled.activeAsset
to remember what the user had open, and set it back to that document after using tiled.open()
. You can also tiled.close()
the Asset afterwards. For a better user experience, you should only close the Asset if it wasn't already opened by the user, which you can check by checking whether tiled.openAssets
contains the Asset you need prior to opening it.
If you need to read or write an arbitrary file as part of your script, Tiled provides TextFile and BinaryFile to help. To use them, create a new TextFile(path, mode)
(or new BinaryFile(path, mode)
). path
is the location of the file to read or write, mode
is one of ReadOnly
, WriteOnly
, ReadWrite
, or Append
. These helper classes provide numerous methods to help you in working with files, which you can find in their documentation. If you're making a custom Map or Tileset format, you'll probably use one of these as part of your reading and/or writing code.
If a script you've written might be useful to other people, please consider sharing it! The tiled-extensions repo accepts pull requests - you don't need to set up a git repo for your scripts to submit them, you can submit files via the GitHub website. Before you submit your script, consider its usability - you know your script well, but other people will not. Some things to consider:
If you write any temporary files, delete them after you're done. If you're storing user preferences in files to save them across executions, consider if you really need this - instead of saving configuration to files, you can store it in custom properties on the user's Project, and you should consider making this configuration optional by letting the user edit some variables somewhere near the top of your script. If you must use files for configuration, read and write them as little as possible.
If your script has a potentially long execution time, or if it is an action modifies files or documents without the possibility of Undo, consider adding a confirmation pop-up (at least when not running via CLI). Accidental executions can and do happen, especially if your script is an action added to a menu or toolbar.
Try to leave the GUI as you found it, except for the changes your user expects the script to make. For example, if you're opening some documents that the user didn't ask for (e.g. tilesets for a new map), close them, and if you need to change the user's selection, you may want to restore it after you're done.
Make icons for scripted tools. This can just be a small PNG file, you don't have to make a full vector icon if you don't want to. Icons usually take up less space than the tool name. You can also reuse any of the images from Tiled's resource directory by setting the icon to the relative path to them from /resources/ and prefixing it with :
, e.g. icon: ":images/16/remove.png"
.
If your script adds an action that doesn't need to be used frequently, consider not adding it to any menus, to avoid cluttering them. As long as the action is named appropriately, users can find it via Search Actions.
Add explanatory tooltips to your Dialog widgets.
Since English is the default language of Tiled and most Tiled users have some knowledge of it, try to write your user-facing texts in English, unless your extension is meant speficially for users of another language. If you want to take it a step further, English comments and English function and variable names can make it easier for more people to troubleshoot your script.
Add a comment at the top of the script that explains what your script does, where to find any actions it adds, and who wrote it (e.g. GitHub username). This way people browsing their script files will know what the file is, and where to go if they need help with it. If your script consists of multiple files, each file should mention which extension it's a part of, and ideally list all the files, so that users can remove them all easily once they no longer need them.
Some examples on this page use art from the following asset packs: