Godot 2D grid building system: paint, place, save
Build a 2D grid building system in Godot 4: five terrain brush modes, buildings on TileMapLayer cells, and a save that restores both layers.
A 2D grid building system is two layers on one grid: terrain cells you paint, and scene objects you
place on top. They look like one system to the player and behave like two to your save file — terrain is
TileMapLayer data, buildings are scene objects with stable IDs. Grid Placement owns both halves; this page
is where they meet, and where a save goes wrong if you treat them as one thing.
Godot 4, Grid Placement 6.2 or newer. The scene rig itself (host, layer, catalog) is the 2D object scene setup guide; object snapping on its own is the TileMap article.
1. Terrain cells are not objects, and that decides your save
A painted cell is not a SceneEntryInstance. It has no scene, no node, no placement_instance_id. It is a
terrain index written into the TileMapLayer. Buildings are the opposite: each one is a scene instance with
a stable ID, and SceneEntryInstance.save() writes its record.
That split is not a limitation, it is the reason both work. A 200×200 painted map costs a few kilobytes of tile data; 40,000 scene instances would not. It also means your save file has two sections, and a restore that forgets one produces the classic "terrain came back, buildings did not" (or the reverse).
Keep the same TileSet and terrain definitions available at load time: saved terrain indices are meaningless
against a different tileset.
2. The five brush modes, and where each one stops
| Mode | Terrain | Objects | Commit |
|---|---|---|---|
SINGLE |
yes | yes | one cell or object |
LINE |
yes | opt-in per entry | drag start→end, re-validated at commit |
RECTANGLE_FILL |
yes | opt-in per entry | drag two corners, fills the region |
RECTANGLE_OUTLINE |
yes | opt-in per entry | drag two corners, paints the perimeter |
FLOOD_FILL |
yes | no | anchor the region, confirm to commit |
FLOOD_FILL is 2D-only: there is no 3D shape math for it, and a 3D request fails closed rather than
painting a guess. Object LINE and rectangle placement are not defaults for every entry — they are an
explicit capability on the entry/profile, so a chair does not accidentally become a drag tool.
Every generated brush is bounded by PlacementSettings.max_terrain_brush_cells:
- default 4096 cells,
- inspector range 1..8192,
- a hard runtime ceiling of 8192.
If a big drag or flood stops earlier than the screen suggested, read that setting before debugging rules.
3. Preview never writes to the real TileMapLayer
While you drag, the plugin previews into its own state; the real TileMapLayer must not mutate until commit.
At commit the whole generated set is validated again, because the world can change between the preview you
looked at and the moment you released the button.
Two consequences for your UI: show the plugin's validity feedback instead of re-deriving it, and treat a cancelled brush as "nothing happened" — cancel clears pending state without committing cells.
4. Costs belong to the layer that is being built
The two layers use different cost sources:
- terrain —
TilePlacementEntry.build_cost(aBuildCost); a brush demolish multiplies it by the number of erased cells, and the entry must havedemolishableon to be removable at all; - objects —
SpendMaterialsRuleByIdvia your inventory bridge, the same id-keyed rule used for 3D.
Both feed the same refund pipeline, so a demolish returns materials through RefundService.refund_ratio
without a second cost table to keep in sync. The pipeline is in
Refund on Demolish.
5. Saving a two-layer build
Objects use SceneEntryInstance.save(true); terrain uses the layer's own cell data. A restore is a
sequence, and the order matters:
- clear the committed placement world,
- rebuild objects with
SceneEntryInstance.instance_from_save(record, parent, duplicate_ids)— one shared duplicate detector for the batch, - restore terrain cells against the same
TileSet, - rebuild occupancy through the 2D restore coordinator,
- validate before the player can build again.
Skipping step 4 is the quiet one: the buildings reappear in the right cells and the next placement is refused, because the occupancy the plugin uses was never rebuilt. The key table and the restore contract are in Save and Load.
6. What goes wrong in 2D
- A brush stops early —
max_terrain_brush_cells. - Terrain loads wrong or not at all — different
TileSet, or terrain indices saved without the tileset version that produced them. - Buildings restore on top of each other — transform restored without occupancy, or two records sharing a
placement_instance_idand no duplicate detector. - A demolished building's cell stays blocked — the node was freed directly instead of through the manipulation path.
- Terrain cannot be removed —
demolishableis off on theTilePlacementEntry.
What this page does not cover
Hex and isometric layouts use the same model with different tile transforms; the shipped demos are the reference (top-down, isometric, platformer, hex). Multiplayer session ownership and the 3D mount rules are their own guides, and the 3D runtime loop is the 3D build-mode article.
Grid Placement is a commercial plugin for Godot 4; the guides and API reference are public, and the browser demos on the Grid Placement page run the real plugin.