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.
A CSS grid with tile images. Only in 3D, and with physical colliders.
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
- Create a scene
tiles.tscnwith aNode3Droot. - The children are
MeshInstance3Dwith unique names (wall_corner,floor_plain,door_wood). - Each mesh has a
StaticBody3Dchild with aCollisionShape3Dso the collision is baked. - Scene → Convert To → MeshLibrary → save as
.tresor.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
- Add a GridMap to the scene.
- In the Inspector, set Mesh Library → your
.tres. - Configure Cell Size (for example,
(2, 2, 2)— 2×2×2-meter cubes). - 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.
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 + MeshLibrary | MultiMeshInstance3D | |
|---|---|---|
| Structure | Grid with a fixed step | Arbitrary positions |
| Editing | In the Scene View with a “brush” | Only from code |
| Number of different meshes | Up to a few hundred | One per node |
| Per-instance variants | Via orientations | Via a transform buffer |
| Animation | No | No (for animated ones — a different approach) |
| Collision | Automatic from MeshLibrary | Must 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
Terrain3Dplugin 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.