Skip to content

Conversation

BCsabaEngine
Copy link

Tiled BinMap Export Plugin

This is a Tiled plugin to dump tilemaps into a brainfuckingly simple binary format.

It ships BinMap as a plugin (which can be compiled into an .so file) as well as an extension (a .js file).

Installation

You'll only need one of these, as they do exactly the same.

JavaScript Extension

Copy src/plugins/binmap/binmap-format.js into ~/.config/tiled/extensions.

Tiled Plugin

  1. Download the tiled source from github.
  2. Install all its dependencies (see its README for details).
  3. Copy src/plugins/binmap directory into the downloaded repository.
  4. Edit src/plugins/plugins.qbs and add "binmap", to the list.
  5. In the repository's main directory, run qbs.

Documentation

The file format specification can be found here. It is the simplest format possible,
just contains a fixed sized header and the uncompressed tile ids for each layer, that's all.

Copy link
Member

@bjorn bjorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually a bit reluctant to merge this because "simple binary formats" come in so many flavors, given that "simple" usually means leaving out a bunch of information and different projects will need different subsets of the information available.

So you apparently only care about tile layers and might not care about image collections, isometric staggered, custom properties, the list of tilesets, parallax settings, layer blend modes, etc. But what's the use of shipping this "binmap" plugin, which presents this subset to many others who'll likely find some of the information they need missing?

As such, I do think the binmap-format.js can be helpful. For those who need just that, they can copy it over, but for those who need a little more or even less information, they can easily tweak it to their needs. But I'm not sure it makes sense to ship this extension with Tiled. What about using it as an example binary map format in mapeditor/tiled-extensions and linking to it from the documentation?

hu16[5] = map.tileWidth;
hu16[6] = map.tileHeight;
/* looks like the Javascript API can't handle hexagonal maps */
hu16[7] = map.Isometric ? 1 : 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map.Isometric doesn't exist. What you're looking for is map.orientation, which will beTileMap.Orthogonal, TileMap.Isometric, TileMap.Staggered or TileMap.Hexagonal.


write: (map, fileName) => {
let numLayers = 0, gid = 1;
/* fix missing firstgid properties on tilesets */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a "fix". "First global IDs" simply don't exist outside of the file format. It's fine to assign them as dynamic properties like this, but you'll want to reword that comment. :-)

/* fix missing firstgid properties on tilesets */
for (let i = 0; i < map.tilesets.length; ++i) {
map.tilesets[i].firstgid = gid;
gid += map.tilesets[i].tileCount;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to note that using tileCount unfortunately only works when there are no gaps in the tile IDs. Image collection tilesets can contain gaps, since when you remove tiles from such a tileset the other tiles don't change their ID.

If you want to support image collection tilesets, you'd currently need to iterate all tiles to find the maximum used tile ID.

case Map::Isometric: header.orientation = O_ISO; break;
case Map::Hexagonal: header.orientation = map->staggerAxis() == Map::StaggerX ? O_HEXH : O_HEXV; break;
default:
mError = QCoreApplication::translate("File Errors", "Unsupported map format.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not support Map::Staggered in this file format? Seems like it'd just take one more possible orientation value. At least, since staggerIndex is not getting exported.

const tile = layer.tileAt(x, y);
/* convert local tile id to global tile id */
iu16[0] = tile == null ? 0 : tile.id + (tile.tileset ? tile.tileset.firstgid : 1);
file.write(item);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a performance perspective I'd suggest writing entire layers at once rather than tile-by-tile.

@BCsabaEngine
Copy link
Author

So you apparently only care about tile layers

Precisely. This format is specifically for those who don't need anything else (99% of beginner projects) and find including an XML parser an overkill and overcomplicated.
This format is deliberately the simplest possible, needs no parsing at all. In return, it stores tile layers only, and nothing else.

As such, I do think the binmap-format.js can be helpful.

Unfortunately it can't work correctly without firstGid properties. Current workaround isn't perfect as you've noted.

What you're looking for is map.orientation

I've tried to use that, but didn't work. I'm open to whatever suggestion to query the TileMap format. Although this isn't an issue in the C++ version, that works well.

I'll have to note that using tileCount unfortunately only works when there are no gaps in the tile IDs.

This is exactly why the C++ implementation is needed because that uses the GidMapper class so it can save global tile ids correctly.

@bjorn
Copy link
Member

bjorn commented Aug 28, 2025

I've tried to use map.orientation, but didn't work. I'm open to whatever suggestion to query the TileMap format. Although this isn't an issue in the C++ version, that works well.

It should work, so please provide more details so we may find out what was going wrong here. A quick test in the Console view didn't reveal a problem to me:

> tiled.activeAsset.orientation
$0 = 1
> TileMap.Orthogonal
$1 = 1

This is exactly why the C++ implementation is needed because that uses the GidMapper class so it can save global tile ids correctly.

While it does highlight a missing property in the scripting API, it can be worked around with minimal code:

maxTileId = tileset.tiles.reduce((maxId, tile) => Math.max(maxId, tile.id), -1);
// instead of tileset.tileCount, use maxTileId + 1

Internally tilesets have a nextTileId property, which is essentially maxTileId + 1 and could be directly exposed in the scripting API to make the above a little faster. To alleviate potential performance issues, one could use this as fallback and still rely on tileCount when isCollection is false.

This format is specifically for those who don't need anything else (99% of beginner projects) and find including an XML parser an overkill and overcomplicated.
This format is deliberately the simplest possible, needs no parsing at all. In return, it stores tile layers only, and nothing else.

While I entirely agree the format is simple to write and read, I still think it's better served as example and a starting point rather than a fixed format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants