~2 мин чтения

GridMap — модульные уровни

MeshLibrary + GridMap для сборки локаций из переиспользуемых блоков.

GridMap — встроенный Godot узел для сборки уровней из модульных блоков на 3D-сетке. Думайте: Minecraft, Dungeon Crawler, Cities Skylines — где мир состоит из переиспользуемых “кирпичиков” размером с тайл.

Идея

  • Сделайте MeshLibrary-ресурс — набор mesh’ей с заранее заданными CollisionShape3D.
  • Положите его на узел GridMap.
  • В Scene View рисуете “кистью” по 3D-сетке — каждая клетка получает выбранный из библиотеки mesh.
  • Godot рендерит весь GridMap одним вызовом на тип меша (внутреннее instancing).

Это и быстрее, и компактнее, чем десять тысяч отдельных MeshInstance3D-узлов.

Веб

CSS-grid с tile-images. Только в 3D, и с physical colliders.

Unity

Создание MeshLibrary

MeshLibrary — это Resource со словарём id → { mesh, collision, navigation, preview }. Создать можно двумя способами:

Способ 1. Конвертировать сцену с детьми-mesh

  1. Создайте сцену tiles.tscn с корнем Node3D.
  2. Дочерние — MeshInstance3D с уникальными именами (wall_corner, floor_plain, door_wood).
  3. Каждый mesh — StaticBody3D ребёнком с CollisionShape3D, чтобы collision запеклась.
  4. Scene → Convert To → MeshLibrary → сохранить .tres или .res.

Способ 2. Программно

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])  # массив Shape3D
ResourceSaver.save(lib, "res://tiles/library.tres")

Использование GridMap

  1. Добавьте на сцену GridMap.
  2. В Inspector укажите Mesh Library → ваш .tres.
  3. Настройте Cell Size (например, (2, 2, 2) — кубики 2×2×2 метра).
  4. В Scene View — GridMap toolbar с палитрой ваших mesh’ей. Кликами рисуете в сетке.

Управление из кода

extends GridMap

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

func _ready() -> void:
    # Простая комната 5×5 из стен и пола
    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)

    # Дверь в (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 — целочисленные координаты ячейки. set_cell_item(pos, item_id, orientation) — последний параметр — поворот блока (24 ориентации куба, через GridMap.get_orthogonal_index_from_basis).

Procedural dungeon generation

GridMap идеально подходит для процедурной генерации:

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. Заполнить случайно
    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)
                # классическое cave-generation правило
                next[x].append(n >= 5 if grid[x][z] else n >= 6)
        grid = next

    # 3. Применить в 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  # за границей — стена
            elif g[nx][nz]:
                count += 1
    return count

Запускаете — получаете cave-style dungeon. С добавлением decoration-layer (статуи, факелы как отдельные ID в MeshLibrary) — выглядит уже как готовый уровень.

Navigation через GridMap

GridMap может автоматически выдавать navmesh-baking. Установите NavigationRegion3D с ребёнком GridMap, и в Bake Navigation Mesh Geometry Source поставьте Group → дети_с_коллизиями. Получаете navmesh без отдельного навиграционного слоя. См. главу про Navigation.

MeshLibrary vs MultiMeshInstance3D

Когда выбирать одно или другое:

GridMap + MeshLibraryMultiMeshInstance3D
СтруктураСетка с фиксированным шагомПроизвольные позиции
РедактированиеВ Scene View “кистью”Только из кода
Кол-во разных mesh’ейДо нескольких сотенОдин на узел
Per-instance variantsЧерез orientationsЧерез transform-buffer
AnimationНетНет (для анимированных — другой подход)
CollisionАвтоматическое из MeshLibraryНужно строить отдельно

Простое правило: сетка → GridMap; trava/декорации без сетки → MultiMesh.

Когда GridMap — НЕ выбор

  • Open-world террейн с нерегулярной топологией — лучше Terrain3D плагин.
  • Динамическая деструкция мира (Minecraft-стиль с реальным изменением) — работает, но оптимизация тонкая; рассмотрите ECS-style approach с GDExtension.
  • Сцены с уникальной геометрией каждого блока — теряется выгода MeshLibrary, проще класть отдельные MeshInstance3D в PackedScene.

Альтернативы

  • TileMap (2D) — то же самое для 2D-сцен.
  • CSG (Constructive Solid Geometry) узлы — для прототипирования архитектуры без модульных мешей. Не для финального уровня.
  • Terrain3D плагин (через AssetLib) — для terrain’а с heightmap.