Архитектура и разработка на Godot 4.x: От узла до системы

Профессиональный курс для инженеров, охватывающий принципы Clean Code, типизацию GDScript 2.0, паттерны проектирования и оптимизацию в Godot 4.3+. Упор на создание масштабируемых систем, Data-Driven подход и глубокое понимание архитектуры движка.

1. Фундамент архитектуры: Экосистема узлов и типизированный GDScript 2.0

Фундамент архитектуры: Экосистема узлов и типизированный GDScript 2.0

Добро пожаловать в курс «Архитектура и разработка на Godot 4.x». Мы начинаем не с написания кода движения персонажа, а с понимания фундамента, на котором строится любой масштабируемый проект в Godot. Ошибка многих разработчиков, приходящих из Unity или Unreal Engine, заключается в попытке навязать движку чуждые ему паттерны. Godot требует мышления в терминах Дерева Сцены (Scene Tree) и Сигналов.

В этой лекции мы разберем атомарную единицу движка — Узел (Node), научимся выбирать правильные инструменты для задач и превратим GDScript из скриптового языка в строгий инженерный инструмент.

Экосистема Узлов: Анатомия Godot

В основе Godot лежит объектно-ориентированная иерархия. Понимание того, от чего наследуется ваш узел, определяет его возможности и стоимость использования.

!Иерархия наследования базовых классов в Godot 4.x

1. Object и Node

Класс Object — это база всего. Он предоставляет доступ к метаданным, скриптам и управлению памятью. Однако, Node — это первый класс, который может быть частью Дерева Сцены.

Ключевые особенности Node: * Имеет имя и путь в дереве. * Управляет жизненным циклом (_ready, _process, tree_entered). * Может иметь дочерние узлы. * Не имеет визуального представления или трансформации (позиции) в пространстве.

Используйте Node, когда вам нужен контроллер логики, менеджер данных или компонент, не привязанный к физическому месту в мире (например, StateMachine или InventoryManager).

2. CanvasItem

Абстрактный класс для всего, что отображается в 2D. Он добавляет: * Отрисовку (draw calls). * Видимость (visible). * Модуляцию цвета (modulate). * Z-индекс (порядок отрисовки).

3. Node2D vs Control

Здесь происходит главное разделение 2D-разработки.

* Node2D: Используется для игровых объектов. Его позиция определяется трансформацией (Transform2D) — позиция, поворот, масштаб. Единицы измерения — пиксели (или метры в физическом движке). * Control: Используется для пользовательского интерфейса (UI). Его позиция и размер определяются якорями (Anchors) и контейнерами. Он умеет обрабатывать фокус ввода и клики мыши лучше, чем Node2D.

> Никогда не используйте Node2D для создания интерфейсов и Control для создания игровых персонажей. Это приведет к проблемам с адаптивностью экрана и обработкой коллизий.

Композиция против Наследования

В классическом ООП мы часто создаем глубокие иерархии: Enemy -> GroundEnemy -> Orc -> EliteOrc. В Godot это считается анти-паттерном, если иерархия становится глубже 2-3 уровней.

Godot поощряет Композицию. Вместо того чтобы наследовать функционал, вы добавляете его как дочерний узел.

Пример: Вместо того чтобы писать код здоровья внутри скрипта Enemy.gd, создайте отдельную сцену HealthComponent (на базе Node), которая обрабатывает урон и смерть. Добавьте этот узел к игроку, врагу или разрушаемому ящику.

!Сравнение жесткой иерархии наследования и гибкой системы композиции

GDScript 2.0 как профессиональный инструмент

С выходом Godot 4.0, GDScript получил значительные улучшения производительности и безопасности типов. Использование динамической типизации (var x = 10) в крупных проектах недопустимо. Это приводит к ошибкам, которые обнаруживаются только во время выполнения, и замедляет работу автодополнения кода.

Статическая типизация

Всегда указывайте типы переменных, аргументов функций и возвращаемых значений. Это ускоряет выполнение кода (движок избегает лишних проверок типов) и делает рефакторинг безопасным.

Плохо:

Хорошо (Clean Code):

Лямбда-функции и Callables

В Godot 4 функции являются первоклассными объектами (Callable). Лямбды позволяют писать лаконичный код для обработки сигналов или сортировки.

Аннотации

Аннотации — это метаданные для движка, которые упрощают код.

* @onready: Инициализирует переменную, когда узел входит в дерево сцены (сразу после _ready). Избавляет от громоздкого кода в функции _ready(). * @export: Выносит переменную в Инспектор редактора. Позволяет геймдизайнерам менять параметры без правки кода. * @rpc: Настраивает удаленный вызов процедур для мультиплеера (будет рассмотрено в следующих модулях).

Техническое задание: Система вооружения

Рассмотрим применение теории на практике.

1. Проблема

Необходимо создать универсальную систему оружия, где разные пушки имеют разные параметры (скорострельность, урон, снаряд), но управляются единым интерфейсом.

2. Решение (The Book of Nodes style)

Мы будем использовать Композицию. Оружие будет отдельным узлом Node2D (так как у него есть позиция дула), который прикрепляется к персонажу. Логика стрельбы будет инкапсулирована в скрипте оружия, а не персонажа.

3. Реализация (The Book of Code style)

Ниже представлен эталонный код базового класса оружия с использованием строгой типизации.

В этом коде мы используем простую математику для позиционирования. Однако, при расчете траектории снаряда часто используется формула движения:

Где — новая позиция объекта (вектор), — текущая позиция, — вектор скорости, а — время, прошедшее с последнего кадра (delta). Это обеспечивает плавное движение независимо от частоты кадров.

4. Предостережение

При разработке такой архитектуры можно допустить критические ошибки:

  • Циклические зависимости (Cyclic Dependencies): Если скрипт Weapon.gd ссылается на класс Player, а Player.gd ссылается на Weapon, Godot может выдать ошибку парсинга или бесконечный цикл загрузки. Решение: используйте сигналы или абстракцию. Оружие не должно знать, кто именно из него стреляет.
  • Утечки памяти при instantiate(): Если вы создаете снаряды и не удаляете их (через queue_free()) после того, как они улетели за экран или попали в цель, игра со временем потребит всю оперативную память. Всегда контролируйте жизненный цикл динамических объектов.
  • 5. Лабораторная работа

    Задание: Создайте сцену HealthComponent (наследуется от Node).

  • Добавьте экспортную переменную max_health (int).
  • Реализуйте типизированную функцию damage(amount: int).
  • Добавьте сигнал died, который эмитится, когда здоровье падает до 0.
  • Подключите этот компонент к простому CharacterBody2D и вызовите нанесение урона по нажатию пробела.
  • В следующей лекции мы углубимся в систему коммуникаций и разберем, почему Синглтоны часто являются злом в архитектуре Godot.

    2. Коммуникация систем: Сигналы, синглтоны и паттерн State Machine

    Коммуникация систем: Сигналы, синглтоны и паттерн State Machine

    В предыдущих лекциях мы научились создавать изолированные компоненты. Теперь перед нами встает главная проблема архитектуры: как заставить эти компоненты общаться друг с другом, не превращая проект в «спагетти-код»? Прямые ссылки (|\vec{v}|v_xv_y|\vec{v}| \approx 0$, мы переходим в состояние Idle.

    Предостережение

    При работе с FSM и сигналами возможны критические ошибки:

  • Состояние-Бог (God State): Не пытайтесь запихнуть всю логику в одно состояние Action, которое обрабатывает и прыжки, и стрельбу. Смысл паттерна — в дроблении логики. Если файл состояния превышает 100 строк, это сигнал к рефакторингу.
  • Потеря ссылок при смене сцен: Если вы подписываетесь на сигналы глобального синглтона внутри локального узла, но не отписываетесь при уничтожении узла, движок может попытаться вызвать метод у удаленного объекта. В Godot 4 метод connect безопасен (связь разрывается автоматически при удалении объекта), но при использовании кастомных реализаций событий будьте внимательны.
  • Лабораторная работа

    Задание: Расширьте созданную систему State Machine.

  • Создайте состояние Dash (Рывок).
  • В Dash: при входе (enter) увеличьте скорость персонажа в 3 раза на 0.2 секунды.
  • Используйте таймер (можно через get_tree().create_timer(0.2) и await), чтобы автоматически вернуть персонажа в состояние Idle или Walk` после завершения рывка.
  • Добавьте проверку: рывок можно делать не чаще, чем раз в 1 секунду (cooldown).
  • В следующем модуле мы займемся физикой и векторной математикой, чтобы сделать движение нашего персонажа по-настоящему отзывчивым.

    3. Игровая физика, математика векторов и обработка коллизий

    Игровая физика, математика векторов и обработка коллизий

    В предыдущих модулях мы создали архитектуру узлов и настроили машину состояний. Теперь нашему персонажу нужно взаимодействовать с миром: ходить по земле, сталкиваться со стенами и обнаруживать врагов. В Godot физика — это не просто гравитация, это сложная система симуляции и математических вычислений.

    В этой лекции мы разберем разницу между игровыми циклами, научимся применять векторную алгебру для геймплейных задач и настроим профессиональную систему коллизий.

    Игровой цикл: _process против _physics_process

    Понимание того, когда выполнять код, критически важно для плавности игры. Godot имеет два основных цикла обновления.

    !Сравнение переменного времени кадра при рендеринге и фиксированного шага физического движка.

    1. _process(delta)

    Вызывается каждый кадр отрисовки. Частота зависит от мощности компьютера и сложности сцены (FPS). * Использование: Интерполяция камеры, анимация UI, визуальные эффекты. * Проблема: Если двигать физическое тело здесь, оно будет двигаться с разной скоростью при разном FPS, а коллизии могут не сработать.

    2. _physics_process(delta)

    Вызывается с фиксированным интервалом (по умолчанию 60 раз в секунду). Этот интервал синхронизирован с физическим движком (PhysX в 3D или Box2D в 2D). * Использование: Перемещение CharacterBody, приложение сил к RigidBody, обработка RayCast. * Важно: delta здесь константна (обычно 0.0166 сек).

    > «Физику двигаем только в физическом процессе». Нарушение этого правила приведет к тому, что персонаж будет проваливаться сквозь пол при падении FPS.

    Векторная математика: Язык движка

    Вектор — это не просто стрелочка. Это фундаментальный инструмент для вычисления отношений между объектами. Рассмотрим две операции, необходимые каждому инженеру.

    Скалярное произведение (Dot Product)

    Позволяет узнать, насколько векторы сонаправлены. Это идеальный инструмент для реализации «конуса зрения» врага или проверки, находится ли объект сзади.

    Формула скалярного произведения:

    Где и — два вектора, и — их длины (магнитуды), а — угол между ними. В Godot функция a.dot(b) возвращает скалярное число.

    Практическое применение: Если векторы нормализованы (длина равна 1): * Результат 1.0: Векторы смотрят в одну сторону. * Результат 0.0: Векторы перпендикулярны. * Результат -1.0: Векторы смотрят в разные стороны.

    Векторное произведение (Cross Product)

    В 3D возвращает вектор, перпендикулярный двум исходным. В 2D (в Godot) возвращает скаляр, который помогает определить, находится ли объект слева или справа от вектора направления.

    Формула магнитуды векторного произведения:

    Где — длина результирующего вектора (или Z-компонента в 2D), и — длины исходных векторов, — угол между ними.

    Нормализация

    Частая ошибка новичков — забывать нормализовать вектор направления. Если вы идете по диагонали, нажимая ВПРАВО и ВВЕРХ, вектор будет (1, 1). Его длина:

    Где — длина вектора. Это значит, что по диагонали персонаж будет двигаться на 41% быстрее. Всегда используйте .normalized(), чтобы привести длину вектора к 1.

    Система Коллизий: Слои и Маски

    В Godot система коллизий построена на битовых масках. Это самый эффективный способ фильтрации взаимодействий.

    * Layer (Слой): Отвечает на вопрос «КТО Я?». (Я — Игрок, Я — Стена, Я — Пуля). * Mask (Маска): Отвечает на вопрос «С КЕМ Я ВЗАИМОДЕЙСТВУЮ?». (Я сканирую Врагов, Я отскакиваю от Стен).

    !Визуализация логики проверки коллизий: маска сканирует слои.

    Совет: Зайдите в Project Settings -> Layer Names -> 2D Physics и дайте слоям имена (Player, World, Enemy, Projectiles). Никогда не используйте «магические числа» (Layer 1, Layer 5) в коде.

    Техническое задание: Кинематический контроллер

    1. Проблема

    Необходимо реализовать движение персонажа с видом сверху (Top-Down), который корректно обрабатывает столкновения со стенами, не застревает на углах и всегда смотрит в сторону курсора мыши.

    2. Решение (The Book of Nodes style)

    Мы используем CharacterBody2D (в Godot 3 назывался KinematicBody2D). * Почему не RigidBody2D? Нам не нужна честная физическая симуляция (инерция, отскок), нам нужен полный контроль над перемещением. Почему не Area2D? Area только обнаруживает* перекрытия, но не обеспечивает физическое выталкивание из стен.

    3. Реализация (The Book of Code style)

    4. Предостережение

    При работе с физикой легко допустить критические ошибки:

  • Туннелирование (Tunneling): Если ваш снаряд летит очень быстро (например, 2000 пикселей в секунду), а FPS упал, за один кадр снаряд может пролететь сквозь тонкую стену, не вызвав события столкновения.
  • Решение:* Используйте RayCast2D для проверки пути перед перемещением или включите Continuous CD (Collision Detection) в настройках тела (дорого для производительности).
  • Изменение физики в _process: Никогда не меняйте позицию физического тела напрямую через position = ... внутри _process, если ожидаете коллизий. Это телепортация, которая ломает движок физики. Используйте только velocity и move_and_slide() внутри _physics_process.
  • 5. Лабораторная работа

    Задание: Реализуйте механику «Удар в спину» (Backstab).

  • Создайте сцену Врага (CharacterBody2D) и Игрока.
  • Когда Игрок атакует Врага, используйте Скалярное произведение (Dot Product), чтобы определить, находится ли Игрок за спиной Врага.
  • Вектор направления взгляда Врага можно получить из его transform.x (если он повернут правильно).
  • Вектор направления от Врага к Игроку: (player.global_position - enemy.global_position).normalized().
  • Если dot_product > 0.5 (угол меньше 60 градусов сзади), нанесите двойной урон.
  • В следующей лекции мы отойдем от кода механик и займемся Data-Driven Design: научимся хранить параметры оружия и врагов не в коде, а в Ресурсах, создавая масштабируемую базу данных игры.

    4. Data-Driven Design: Система ресурсов и персистентность данных

    Data-Driven Design: Система ресурсов и персистентность данных

    В предыдущих модулях мы научили нашего персонажа двигаться, стрелять и взаимодействовать с физическим миром. Однако, если вы посмотрите на свой код сейчас, вы, вероятно, увидите множество «магических чисел»: урон оружия прописан в скрипте пули, здоровье врага — в скрипте врага. Это путь к техническому долгу.

    В этой лекции мы перейдем к Data-Driven Design (архитектуре, управляемой данными). Мы научимся отделять логику (поведение) от данных (характеристик), используя мощнейший инструмент Godot — систему Ресурсов (Resource), а также реализуем сохранение прогресса через JSON.

    Философия Data-Driven: Логика vs Данные

    В классическом программировании мы часто смешиваем данные и методы. В игровой разработке это приводит к проблемам масштабирования. Представьте, что у вас 100 видов мечей. Создавать 100 скриптов, наследующихся от BaseSword, — это архитектурное самоубийство.

    Правильный подход:

  • Логика (Node): Один скрипт SwordController, который умеет наносить урон, проигрывать анимацию и тратить прочность.
  • Данные (Resource): 100 файлов ресурсов (.tres), каждый из которых хранит текстуру, значение урона и название конкретного меча.
  • !Визуализация разделения ответственности: один контроллер поведения использует разные контейнеры данных.

    Что такое Resource?

    Resource — это базовый класс для всех типов данных в Godot. Текстуры, меши, аудиосэмплы, сцены — всё это ресурсы. Главная особенность ресурсов — они ссылочные (reference-counted) и разделяемые (shared).

    Если 50 врагов используют один и тот же ресурс GoblinStats.tres, в памяти будет находиться только один экземпляр этого ресурса. Это критически важно для оптимизации памяти.

    Техническое задание: База данных вооружения

    1. Проблема

    Нам необходимо создать систему, позволяющую геймдизайнерам добавлять новое оружие в игру, не открывая редактор кода и не прося программистов создать новый класс.

    2. Решение (The Book of Nodes style)

    Мы создадим кастомный ресурс WeaponData. Это не узел, он не будет находиться в дереве сцены. Это файл на диске, который мы будем «скармливать» нашему оружию.

    3. Реализация (The Book of Code style)

    Сначала определим структуру данных. Ключевое слово class_name обязательно, чтобы ресурс появился в контекстном меню создания файлов в редакторе Godot.

    Теперь обновим наш скрипт оружия из первой лекции, чтобы он использовал эти данные.

    Теперь в редакторе вы можете нажать ПКМ в файловой системе -> Create New... -> Resource -> WeaponData, настроить параметры в Инспекторе и перетащить файл в слот Stats вашего оружия.

    Персистентность: Сохранение и загрузка

    Ресурсы отлично подходят для статических данных (баланс игры). Но как хранить динамические данные (сохранения игрока, инвентарь)?

    Существует два основных подхода:

  • ResourceSaver/ResourceLoader: Можно сохранить весь ресурс игрока в файл .tres или .res. Это быстро, но небезопасно для передачи файлов между игроками (возможна инъекция вредоносного кода через скрипты внутри ресурсов).
  • JSON Serialization: Стандарт индустрии. Мы вручную преобразуем объекты в словари, а словари — в текстовый формат JSON.
  • Реализация SaveManager

    Создадим статический класс (или Autoload) для управления сохранениями.

    Пример использования в игроке

    Предостережение

    Работа с ресурсами таит в себе две критические ловушки для новичков.

    1. Проблема разделяемого состояния (Shared State)

    По умолчанию ресурсы кэшируются. Если вы измените свойство ресурса во время игры, оно изменится для всех объектов, использующих этот ресурс.

    Пример катастрофы: У вас есть ресурс EnemyStats с hp = 100. Вы назначаете его всем гоблинам. Когда вы бьете одного гоблина и в коде делаете stats.hp -= 10, здоровье уменьшается у всех гоблинов на уровне. Более того, если вы запустите игру в редакторе, значение hp в файле ресурса может измениться навсегда.

    Решение: Никогда не меняйте данные внутри Resource во время игры, если это не уникальный экземпляр. Храните текущее здоровье (current_hp) в скрипте узла (Node), а максимальное (max_hp) берите из ресурса.

    2. Циклические зависимости ресурсов

    Если ResourceA имеет переменную типа ResourceB, а ResourceB ссылается обратно на ResourceA, Godot может не загрузить их корректно или вызвать переполнение стека при инициализации.

    Решение: Используйте слабые ссылки или пересмотрите архитектуру. Ресурсы должны образовывать древовидную структуру, а не сетевую.

    Лабораторная работа

    Задание: Создайте систему инвентаря на основе ресурсов.

  • Создайте ресурс ItemData с полями: id (String), display_name (String), weight (float).
  • Создайте ресурс Inventory, который содержит экспортируемый массив Array[ItemData].
  • Реализуйте функцию calculate_total_weight() внутри ресурса Inventory, которая возвращает сумму весов всех предметов.
  • Напишите скрипт, который сохраняет список ID предметов инвентаря в JSON и умеет восстанавливать инвентарь, загружая соответствующие ресурсы по их ID (используйте load("res://items/" + id + ".tres")).
  • В следующей лекции мы займемся Интерфейсами и UX, чтобы визуализировать наши данные: создадим адаптивный инвентарь и полоски здоровья, реагирующие на изменение данных.

    5. Проектирование интерфейсов, жизненный цикл объектов и оптимизация

    Проектирование интерфейсов, жизненный цикл объектов и оптимизация

    Мы прошли долгий путь: от понимания узлов и сигналов до физики и баз данных на ресурсах. Теперь у нас есть работающая логика и данные, но нет способа отобразить их игроку и обеспечить стабильную производительность при большом количестве объектов.

    В этой, завершающей основной блок, лекции мы разберем три критически важных аспекта профессиональной разработки: создание адаптивных интерфейсов (UI), управление жизненным циклом сущностей и паттерны оптимизации памяти.

    Архитектура UI: Контейнеры против Якорей

    Разработка интерфейсов в Godot строится на узлах Control. Главная ошибка новичков — попытка расставлять кнопки и панели вручную, используя координаты пикселей. Это работает на вашем мониторе, но ломается на любом другом разрешении экрана.

    Система Контейнеров

    Godot предлагает мощную систему автоматической верстки. Вместо того чтобы задавать позицию (position), вы должны задавать правила поведения.

    !Сравнение жесткой верстки и системы контейнеров Godot.

    Основные узлы верстки:

    * HBoxContainer / VBoxContainer: Выстраивают дочерние элементы в горизонтальный или вертикальный ряд. Игнорируют ручную позицию детей. * GridContainer: Выстраивает элементы в сетку с заданным количеством колонок. * MarginContainer: Добавляет отступы от краев для своего содержимого. * PanelContainer: Автоматически подстраивает фон (Panel) под размер содержимого.

    Size Flags: Expand и Fill

    Чтобы элементы внутри контейнеров вели себя предсказуемо, необходимо понимать настройки Size Flags:

  • Fill (Заполнение): Элемент займет все доступное ему пространство по выбранной оси.
  • Expand (Расширение): Элемент будет «расталкивать» соседей, требуя больше места, если оно есть в родительском контейнере.
  • Ресурсы темы (Theme)

    Никогда не настраивайте шрифты и цвета в инспекторе для каждой кнопки отдельно. Используйте ресурс Theme. Это CSS для Godot. Создав один файл .tres с темой и назначив его корневому узлу UI, вы автоматически примените стиль ко всем дочерним элементам. Это позволяет менять визуальный стиль всей игры за секунду.

    Жизненный цикл: Рождение и Смерть объектов

    В динамической игре объекты постоянно появляются (пули, враги, эффекты) и исчезают. Неправильное управление этим процессом ведет к утечкам памяти и падению FPS.

    Инстанцирование (Instantiate)

    Сцены в Godot хранятся на диске как PackedScene. Чтобы добавить сцену в игру, нужно создать её экземпляр.

    Удаление (Queue Free)

    В Godot есть два метода удаления узлов:

  • free(): Удаляет объект немедленно. Опасно! Если в этот момент движок обрабатывает физику этого объекта или другой скрипт пытается к нему обратиться, игра вылетит (crash).
  • queue_free(): Помечает объект на удаление. Движок безопасно удалит его в конце текущего кадра, когда закончит все вычисления. Используйте этот метод в 99% случаев.
  • Оптимизация: Математика производительности

    Производительность игры измеряется не только в кадрах в секунду (FPS), но и во времени кадра (Frame Time).

    Формула зависимости времени кадра от FPS:

    Где — время, доступное для отрисовки одного кадра в миллисекундах, — количество миллисекунд в секунде, а — целевое количество кадров в секунду.

    Для стабильных 60 FPS у нас есть всего:

    Где — бюджет времени на один кадр в миллисекундах (примерно 16.66 мс). Если ваши скрипты, физика и рендеринг суммарно занимают 17 мс, игра начнет тормозить.

    Операции instantiate() и queue_free() являются «дорогими». Они требуют выделения памяти, парсинга сцены и перестройки дерева. Если вы создаете и удаляете 100 пуль в секунду, процессор будет перегружен.

    Паттерн Object Pooling (Пул объектов)

    Решение проблемы частых созданий/удалений — переиспользование.

    !Схема работы пула объектов: вместо удаления объект скрывается и возвращается в очередь.

    Вместо queue_free() мы скрываем объект и отключаем его обработку. Вместо instantiate() мы берем уже готовый скрытый объект из массива.

    Техническое задание: Динамический инвентарь

    1. Проблема

    Необходимо отобразить содержимое инвентаря игрока (массив ресурсов из прошлой лекции) в виде сетки иконок. Интерфейс должен обновляться при изменении данных и корректно отображаться на разных экранах.

    2. Решение (The Book of Nodes style)

    Мы будем использовать: * GridContainer: Для автоматического размещения слотов в сетку. * PanelContainer: Для фона каждого слота. * TextureRect: Для отображения иконки предмета. * Resource (Inventory): Как источник данных.

    3. Реализация (The Book of Code style)

    Создадим сцену слота InventorySlot.tscn и скрипт менеджера UI.

    4. Предостережение

    При работе с UI и жизненным циклом возможны критические ошибки:

  • Блокировка ввода (Input Blocking): По умолчанию узлы Control (особенно Panel и Button) поглощают события мыши. Если вы разместите прозрачную панель на весь экран поверх игры, вы не сможете кликать по игровым объектам.
  • Решение:* Установите свойство Mouse -> Filter в Ignore или Pass для элементов, которые не должны перехватывать клики.
  • Утечка памяти через Callable: Если вы подписываете UI на сигналы глобального ресурса (как в примере выше inventory_data.changed.connect), но не отписываетесь при уничтожении UI, ресурс будет хранить ссылку на удаленный узел. В Godot 4 это обрабатывается лучше, но хорошим тоном считается явная отписка или использование флага CONNECT_ONE_SHOT для одноразовых событий.
  • 5. Лабораторная работа

    Задание: Реализуйте простой Object Pool для пуль.

  • Создайте класс BulletPool (наследуется от Node).
  • При старте (_ready) создайте 20 экземпляров пули, добавьте их в сцену, деактивируйте (visible = false, set_process(false)) и добавьте в массив available_bullets.
  • Реализуйте метод get_bullet(pos: Vector2, dir: Vector2), который берет пулю из массива, активирует её и выставляет позицию.
  • В скрипте пули замените queue_free() на вызов метода пула return_bullet(self), который скрывает пулю и возвращает её в массив.
  • Это завершает наш базовый курс. Вы научились создавать архитектуру, управлять данными, физикой и интерфейсом. Теперь вы готовы создавать полноценные системы, а не просто наборы скриптов.