Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

deforestpeg

2
Posts
2
Topics
1
Followers
9
Following
A member registered 5 days ago · View creator page →

Creator of

Recent community posts

Posting this because it is undocumented and I had to decode it from engine source, so hopefully it saves someone the afternoon.


If you write .tscn files outside the Godot editor (map generators, level converters, build scripts), a TileMapLayer stores its painted cells in a single tile_map_data PackedByteArray. Layout, little-endian:


Bytes 0-1: uint16 format version. It is 0 in every release from 4.3 through current master, since the enum still has one entry.


Then one 12-byte record per painted cell, in any order:

  +0  int16   cell x   (negative coords are two's complement, so -1 is FF FF)

  +2  int16   cell y

  +4  uint16  source id

  +6  uint16  atlas coords x

  +8  uint16  atlas coords y

  +10 uint16  alternative tile


Total length is always 2 + 12n. The engine checks (len - 2) % 12 == 0 and throws "Corrupted tile map data" otherwise, which is how I found that rule.


Things that cost me time:


Empty cells are never stored. There is no -1 sentinel, so an empty layer is an empty array rather than a grid of blanks.


Flip flags ride in the alternative field, OR-ed on: FLIP_H 0x1000, FLIP_V 0x2000, TRANSPOSE 0x4000.


Both text encodings parse. Since 4.2 the parser accepts PackedByteArray(0, 0, 1, ...) and PackedByteArray("base64..."). Godot's own saver writes decimal while every array is under 64 bytes, then switches the whole file to format=4 with base64. Writing format=3 with base64 is legal and loads in 4.3 through master; the editor just normalises it on the next save.


Worked example, six cells, source 0, alternative 0, at (0,0) (1,0) (2,0) (0,1) (1,1) and (-1,-1):


00 00                                    version 0

00 00 00 00  00 00  00 00 00 00  00 00   (0,0)   atlas 0,0

01 00 00 00  00 00  01 00 00 00  00 00   (1,0)   atlas 1,0

02 00 00 00  00 00  02 00 00 00  00 00   (2,0)   atlas 2,0

00 00 01 00  00 00  00 00 01 00  00 00   (0,1)   atlas 0,1

01 00 01 00  00 00  01 00 01 00  00 00   (1,1)   atlas 1,1

FF FF FF FF  00 00  03 00 00 00  00 00   (-1,-1) atlas 3,0


Version trap: TileMapLayer only exists in 4.3 and later, so a scene using it will not open in 4.2. If you need older support, the deprecated TileMap node holds the same 12 bytes reinterpreted as three int32s per cell in layer_0/tile_data, with a node-level format = 2 selecting that decoding.


One more: the per-tile physics polygons in the companion .tres use tile-CENTER origin, not top-left. A 32px tile's full-square collision is PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16). I had that one wrong for a while and the shapes come out offset by half a tile.


All of the above verified by writing files by hand and opening them in 4.7.1.

I got tired of writing the same BSP splitter and cellular-automata pass at the start of every roguelike prototype, so I built a generator that hands you the finished thing instead of a picture of it.


MapForge: https://deforestpeg.itch.io/mapforge


Pick dungeon, caves, or joined. Tune room size, density, corridor width, wall fill and smoothing, and it regenerates as you drag. Then download a Godot 4 scene zip: a .tscn with two TileMapLayers, the .tres TileSet, the tileset PNG, and a three-step import note. The Walls layer already carries collision on physics layer 1, the Floors layer carries none. Drag it into your level and walk around. There is a Tiled .tmx export too, with a collision polygon on every wall tile and a wangset.


Seeds are deterministic, so the same seed rebuilds the same map and you can paste it into a bug report.


Honest limits, because I would want to know before clicking: Godot 4.3 or newer only, since scenes use TileMapLayer, which does not exist in 4.2. Maps cap at 256 cells per side. It ships its own small stone tileset and has no art import yet, so using your own art means swapping the exported PNG for a sheet in the same layout. Collision is one full-tile box per wall, fine for grid movement and not for slopes.


The free browser demo on the page is the whole tool, capped at 40x30 maps. Paid is uncapped and is a single offline HTML file, pay what you want from $5.


I also wrote up the byte layout of Godot's tile_map_data while building the exporter, since it is undocumented and I decoded it from engine source: https://deforestpeg.itch.io/mapforge/devlog/1605311/godot-4s-tile-map-data-decod...


Sibling tool if you need the tileset itself rather than the map: https://deforestpeg.itch.io/terrainforge turns a tileset PNG into a Godot 4 TileSet with the 47 blob peering cases and traced collision filled in.


Happy to answer anything, and I genuinely want to hear if the export misbehaves in your project.