~3 min read

GridMap — modular levels

MeshLibrary + GridMap for assembling locations from reusable blocks.

GridMap is a built-in Godot node for assembling levels from modular blocks on a 3D grid. Think: Minecraft, a Dungeon Crawler, Cities Skylines — where the world consists of reusable tile-sized “bricks”.

The idea

  • Make a MeshLibrary resource — a set of meshes with predefined CollisionShape3D.
  • Assign it to a GridMap node.
  • In the Scene View you “paint” with a brush over the 3D grid — each cell gets the mesh selected from the library.
  • Godot renders the whole GridMap with one draw call per mesh type (internal instancing).

This is both faster and more compact than ten thousand separate MeshInstance3D nodes.

Web

A CSS grid with tile images. Only in 3D, and with physical colliders.

Unity

Creating a MeshLibrary

MeshLibrary is a Resource with a dictionary id → { mesh, collision, navigation, preview }. You can create it in two ways:

Method 1. Convert a scene with mesh children

  1. Create a scene tiles.tscn with a Node3D root.
  2. The children are MeshInstance3D with unique names (wall_corner, floor_plain, door_wood).
  3. Each mesh has a StaticBody3D child with a CollisionShape3D so the collision is baked.
  4. Scene → Convert To → MeshLibrary → save as .tres or .res.

Method 2. Programmatically

var lib := MeshLibrary.new()
var id := 0
lib.create_item(id)
lib.set_item_name(id, "wall_corner")
lib.set_item_mesh(id, preload("res://meshes/wall_corner.obj"))
lib.set_item_shapes(id, [my_box_shape])  # array of Shape3D
ResourceSaver.save(lib, "res://tiles/library.tres")

Using GridMap

  1. Add a GridMap to the scene.
  2. In the Inspector, set Mesh Library → your .tres.
  3. Configure Cell Size (for example, (2, 2, 2) — 2×2×2-meter cubes).
  4. In the Scene View — the GridMap toolbar with a palette of your meshes. Click to paint in the grid.

Controlling from code

extends GridMap

const FLOOR := 0
const WALL := 1
const DOOR := 2

func _ready() -> void:
    # A simple 5×5 room of walls and floor
    for x in range(5):
        for z in range(5):
            set_cell_item(Vector3i(x, 0, z), FLOOR)
            if x == 0 or x == 4 or z == 0 or z == 4:
                set_cell_item(Vector3i(x, 1, z), WALL)

    # Door at (2, 1, 0)
    set_cell_item(Vector3i(2, 1, 0), DOOR)

func is_cell_empty(pos: Vector3i) -> bool:
    return get_cell_item(pos) == GridMap.INVALID_CELL_ITEM

Vector3i — integer cell coordinates. In set_cell_item(pos, item_id, orientation) the last parameter is the block’s rotation (24 cube orientations, via GridMap.get_orthogonal_index_from_basis).

Procedural dungeon generation

GridMap is ideal for procedural generation:

extends GridMap

const FLOOR := 0
const WALL := 1

@export var size_x: int = 30
@export var size_z: int = 30
@export var iterations: int = 5

func _ready() -> void:
    randomize()
    _generate_cellular()

func _generate_cellular() -> void:
    # 1. Fill randomly
    var grid: Array = []
    for x in size_x:
        grid.append([])
        for z in size_z:
            grid[x].append(randf() < 0.45)

    # 2. Cellular automata smoothing
    for _i in iterations:
        var next: Array = []
        for x in size_x:
            next.append([])
            for z in size_z:
                var n := _count_neighbors(grid, x, z)
                # the classic cave-generation rule
                next[x].append(n >= 5 if grid[x][z] else n >= 6)
        grid = next

    # 3. Apply to the GridMap
    for x in size_x:
        for z in size_z:
            set_cell_item(Vector3i(x, 0, z), FLOOR)
            if grid[x][z]:
                set_cell_item(Vector3i(x, 1, z), WALL)

func _count_neighbors(g: Array, x: int, z: int) -> int:
    var count := 0
    for dx in [-1, 0, 1]:
        for dz in [-1, 0, 1]:
            if dx == 0 and dz == 0:
                continue
            var nx := x + dx
            var nz := z + dz
            if nx < 0 or nx >= size_x or nz < 0 or nz >= size_z:
                count += 1  # out of bounds — a wall
            elif g[nx][nz]:
                count += 1
    return count

Run it and you get a cave-style dungeon. Add a decoration layer (statues, torches as separate IDs in the MeshLibrary) and it already looks like a finished level.

Navigation via GridMap

GridMap can automatically provide navmesh baking. Set up a NavigationRegion3D with a GridMap child, and in Bake Navigation Mesh set Geometry Source to Group → children_with_collisions. You get a navmesh without a separate navigation layer. See the Navigation chapter.

MeshLibrary vs MultiMeshInstance3D

When to choose one or the other:

GridMap + MeshLibraryMultiMeshInstance3D
StructureGrid with a fixed stepArbitrary positions
EditingIn the Scene View with a “brush”Only from code
Number of different meshesUp to a few hundredOne per node
Per-instance variantsVia orientationsVia a transform buffer
AnimationNoNo (for animated ones — a different approach)
CollisionAutomatic from MeshLibraryMust be built separately

A simple rule: grid → GridMap; grass/decorations without a grid → MultiMesh.

When GridMap is NOT the choice

  • Open-world terrain with irregular topology — the Terrain3D plugin is better.
  • Dynamic destruction of the world (Minecraft-style with real changes) — it works, but the optimization is delicate; consider an ECS-style approach with GDExtension.
  • Scenes with unique geometry per block — the MeshLibrary benefit is lost; it’s simpler to place separate MeshInstance3D in a PackedScene.

Alternatives

  • TileMap (2D) — the same thing for 2D scenes.
  • CSG (Constructive Solid Geometry) nodes — for prototyping architecture without modular meshes. Not for a final level.
  • Terrain3D plugin (via the AssetLib) — for terrain with a heightmap.