Генерация уровня и расстановка объектов

Расставлять сотни монет вручную утомительно — гораздо удобнее создавать объекты кодом по правилам.

Суть: загружаешь сцену-заготовку, в цикле делаешь её копии (instantiate) и добавляешь в дерево (add_child) на нужные позиции.

Когда уровень большой, ставить каждую монетку мышкой невыносимо. Программная генерация решает это: ты делаешь одну сцену-заготовку (монетку, врага, платформу) и кодом создаёшь сколько угодно её копий, раскладывая по сетке или случайно. Создание копии сцены называется инстансированием: preload загружает заготовку, instantiate() делает её свежую копию, add_child() добавляет копию в дерево, чтобы она появилась в игре.

extends Node2D

@export var coin_scene: PackedScene
@export var rows: int = 3
@export var cols: int = 5
@export var spacing: float = 64.0

func _ready() -> void:
    for row in range(rows):
        for col in range(cols):
            var coin := coin_scene.instantiate()
            coin.position = Vector2(col * spacing, row * spacing)
            add_child(coin)

Эти три строки внутри двойного цикла раскладывают аккуратную сетку монет. Меняешь rows и cols в инспекторе — меняется уровень, и ни одну монетку не нужно ставить вручную.

Полезно увидеть здесь дверь к процедурной генерации — приёму, на котором держатся целые жанры. Если вместо ровной сетки подставлять случайные позиции, разное число объектов и разные типы заготовок, каждый запуск игры будет рождать новый, неповторимый уровень. Так устроены roguelike-игры и бесконечные раннеры: контент создаётся кодом на лету, а не рисуется вручную. Начав с простой сетки монет, ты уже владеешь базовым кирпичом этого подхода — цикл, заготовка, instantiate, add_child. Дальше всё упирается лишь в то, по каким правилам выбирать позиции и что именно порождать.

Как работает под капотом

Сцена-заготовка хранится как PackedScene — упакованный шаблон. instantiate() разворачивает шаблон в новый набор узлов (свежую копию со своими переменными). add_child() вставляет корень копии в дерево как потомка, после чего движок начинает её обновлять и рисовать. Цикл просто повторяет это нужное число раз, подставляя разные позиции.

spacing = 64
rows, cols = 3, 5
coins = []

for row in range(rows):
    for col in range(cols):
        pos = (col * spacing, row * spacing)
        coins.append(pos)        # "add_child" копии монеты

print("Создано монет:", len(coins))
print("Первые три позиции:", coins[:3])
print("Последняя позиция: ", coins[-1])

Та же логика на Python ▶. Двойной цикл по строкам и столбцам считает позицию каждой монеты как (col * spacing, row * spacing). В Godot к каждой такой позиции добавляется реальная копия сцены через instantiate и add_child.

Стоит заранее подумать об уборке за собой. Если объекты не только создаются, но и должны исчезать (собранные монеты, убитые враги, улетевшие за экран пули), важно вовремя звать queue_free для ненужных копий. Иначе они тихо копятся в дереве, едят память и потихоньку роняют FPS — особенно в бесконечных или длинных уровнях. Хорошее правило: на каждый instantiate подумай, когда и как этот объект покинет игру. Аккуратное создание вместе с аккуратным удалением держит сцену лёгкой и быструю даже после долгой игры, и это отличает крепкий проект от того, что тормозит через пять минут.

Частые ошибки

Первая ошибка — забыть add_child после instantiate: копия создана, но её нет в дереве, поэтому она невидима и неактивна. Создал — добавь. Вторая — переиспользовать один и тот же экземпляр вместо новых копий: тогда «монеты» окажутся одним объектом, прыгающим по позициям. Каждой монете — свой instantiate(). Третья — генерировать тысячи объектов без меры: уровень начнёт тормозить; ограничивай количество. Четвёртая — путать preload (на этапе загрузки) и load (во время игры) для тяжёлых сцен.

Best practices

Делай объект один раз как отдельную сцену, потом порождай копии кодом. Параметры генерации (число рядов, отступ, плотность) выноси в @export, чтобы перестраивать уровень из инспектора. Для каждой новой штуки зови instantiate() и обязательно add_child(). Считай позиции через индексы цикла и шаг (col * spacing). Ограничивай количество объектов, чтобы держать стабильный FPS.

Итоги: объекты уровня удобно создавать кодом: preload заготовки, instantiate() копии, add_child() в дерево. Двойной цикл по строкам и столбцам раскладывает сетку, позиция = индекс * шаг. Параметры генерации выноси в @export. Каждой копии — свой instantiate, всегда добавляй её в дерево и ограничивай число объектов.

Проверьте себя
1. Что нужно сделать после coin_scene.instantiate(), чтобы монета появилась в игре?
AНичего, она уже в игре
BВызвать add_child(coin), чтобы добавить копию в дерево
CВызвать queue_free()
DПерезапустить игру
2. Как двойной цикл раскладывает монеты в аккуратную сетку?
AСлучайно разбрасывает их
BПозиция каждой монеты считается как (col * spacing, row * spacing)
CВсе монеты в одной точке
DСетку нельзя сделать кодом