Разработка 2.5D RPG на Godot: Углубленный GDScript

Практический курс, сфокусированный на синтаксисе GDScript и архитектурных паттернах для создания ролевой игры. Вы изучите работу с типами данных, физикой, сигналами и ресурсами в контексте режима совместимости Godot 4.

1. Основы синтаксиса GDScript: строгая типизация и настройка 2.5D окружения

Основы синтаксиса GDScript: строгая типизация и настройка 2.5D окружения

Добро пожаловать в курс по разработке 2.5D RPG на Godot. Мы создадим ролевую игру с видом сверху, используя мощь Godot 4 и углубленные возможности GDScript. В этой вводной статье мы не просто создадим проект, а заложим фундамент профессиональной архитектуры кода, используя строгую типизацию, и настроим сцену для специфического визуального стиля 2.5D.

Выбор рендеринга: Почему Compatibility?

При создании нового проекта в Godot 4 перед нами встает выбор между тремя режимами рендеринга: Forward+, Mobile и Compatibility. Для нашей задачи — Top-Down 2.5D RPG — мы выбираем Compatibility (Совместимость).

Этот режим использует OpenGL ES 3.0 (или WebGL 2.0 для веба). Вот почему это важно для нашего жанра:

* Пиксельная точность: Compatibility отлично справляется с рендерингом 2D-спрайтов в 3D-пространстве без лишних артефактов сглаживания, свойственных более сложным конвейерам. * Охват аудитории: RPG — жанр, популярный на самых разных устройствах, включая старые ноутбуки и мобильные телефоны. Compatibility обеспечивает максимальную поддержку железа. * Простота освещения: Для стилизованной 2.5D картинки нам не нужны тяжелые функции вроде SDFGI или Volumetric Fog, которые потребляют ресурсы в Forward+.

Концепция 2.5D в Godot

Термин "2.5D" может означать разные вещи. В рамках этого курса мы будем использовать подход "3D мир + 2D спрайты" (Billboarding). Это позволяет нам использовать 3D-физику, навигацию и освещение, сохраняя при этом ретро-эстетику спрайтов.

!Схема работы 2.5D: Камера смотрит на 3D-мир, где персонажи представлены плоскими спрайтами, всегда обращенными к зрителю.

Для достижения эффекта "Top-Down" без перспективных искажений (когда дальние объекты кажутся меньше), мы будем использовать камеру в режиме Orthogonal (Ортогональная проекция).

Углубленный GDScript: Строгая типизация

Главная ошибка новичков в RPG-проектах — использование динамической типизации везде. RPG — это сложная система характеристик, инвентаря и состояний. Без строгой типизации отладка превратится в кошмар.

Синтаксис статической типизации

В GDScript 2.0 статическая типизация не только помогает избежать ошибок, но и улучшает производительность и работу автодополнения. Рассмотрим базовый синтаксис:

Если вы попытаетесь присвоить переменной max_health строковое значение, редактор выдаст ошибку еще до запуска игры. Это называется compile-time error check.

Выведение типов (Type Inference)

Чтобы писать меньше кода, можно использовать оператор :=. Godot сам определит тип переменной на основе присваиваемого значения:

Важно: Используйте := только тогда, когда значение очевидно. Если значение возвращается из сложной функции, лучше указать тип явно для читаемости.

Типизация функций

В RPG функции часто рассчитывают урон или проверяют условия. Мы обязаны указывать типы аргументов и возвращаемого значения.

Стрелка -> указывает, какой тип данных вернет функция. Если функция ничего не возвращает, используется void.

Типизированные массивы

Инвентарь — классический пример использования массивов. В старом GDScript массив мог содержать всё вперемешку: числа, строки, объекты. В нашем курсе мы будем использовать типизированные массивы:

Попытка добавить в quest_log число вызовет ошибку. Это критически важно для стабильности данных сохранения.

Математика движения: Нормализация вектора

При разработке управления в Top-Down играх часто возникает проблема: движение по диагонали происходит быстрее, чем по прямой. Это происходит потому, что вектор движения складывается из скоростей по осям X и Z (в 3D).

Если мы нажмем "Вверх" (Z = -1) и "Вправо" (X = 1), наш вектор будет . Длина этого вектора рассчитывается по теореме Пифагора:

Где: * — длина (магнитуда) вектора скорости. * — компонент вектора по оси X. * — компонент вектора по оси Z. * — операция извлечения квадратного корня.

Подставив значения, получаем:

Где: * — итоговая длина вектора.

Это означает, что персонаж движется по диагонали в 1.4 раза быстрее. Чтобы исправить это, мы должны нормализовать вектор. Нормализация — это приведение длины вектора к 1 (единичный вектор), сохраняя его направление.

Формула нормализации вектора:

Где: * — нормализованный вектор (единичный). * — исходный вектор. * — длина исходного вектора.

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

Организация кода: class_name и Enums

Для большой RPG нам нужно, чтобы скрипты знали друг о друге без постоянного использования get_node или preload. Для этого мы используем ключевое слово class_name.

Глобальные классы

Добавив class_name в начало скрипта, мы регистрируем его как новый тип данных в редакторе.

Теперь в любом другом скрипте мы можем писать:

Перечисления (Enums) для состояний

Персонаж RPG всегда находится в каком-то состоянии: стоит, бежит, атакует, оглушен. Использование строк (например, if state == "run") — плохая практика, так как легко допустить опечатку. Используйте enum:

Под капотом IDLE будет равен 0, MOVE — 1 и так далее, но в коде мы работаем с понятными именами.

Практическая настройка проекта

Перед тем как перейти к следующему уроку, выполните следующие действия в Godot:

  • Создайте новый проект с рендерером Compatibility.
  • Зайдите в Project -> Project Settings -> General -> Debug -> GDScript.
  • Включите все настройки, связанные с Typed GDScript (установите Warning или Error). Это заставит редактор подсвечивать строки, где вы забыли указать типы.
  • Создайте базовую сцену Main.tscn с узлом Node3D.
  • В следующей статье мы приступим к созданию контроллера персонажа и реализуем передвижение с учетом нормализации векторов.

    2. Физика и управление: работа с CharacterBody3D, Vector3 и спрайтами в 3D

    Физика и управление: работа с CharacterBody3D, Vector3 и спрайтами в 3D

    В предыдущей статье мы настроили проект, выбрали режим рендеринга Compatibility и обсудили важность строгой типизации в GDScript. Теперь пришло время оживить наш мир. В 2.5D RPG персонаж — это не просто картинка, а физическое тело, перемещающееся в трехмерном пространстве, но выглядящее как 2D-спрайт.

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

    Архитектура игрока: CharacterBody3D

    В Godot 4 для создания персонажей, управляемых игроком или скриптом, используется узел CharacterBody3D (ранее известный как KinematicBody в Godot 3). В отличие от RigidBody3D, который подчиняется всем законам физики (гравитация, инерция, отскоки), CharacterBody3D движется только тогда, когда мы явно указываем ему это сделать через код. Это идеально подходит для RPG, где нам нужен точный контроль над передвижением.

    Структура сцены игрока

    Создайте новую сцену и выберите CharacterBody3D в качестве корневого узла. Назовите его Player. Для корректной работы физики и отображения нам понадобятся дочерние узлы:

  • CollisionShape3D: Определяет физические границы персонажа. Для Top-Down RPG лучше всего подходит форма CylinderShape3D или CapsuleShape3D. Поскольку мы смотрим сверху, цилиндр обеспечивает наиболее предсказуемое скольжение вдоль стен.
  • Sprite3D: Этот узел будет отображать нашего персонажа. В 2.5D мы не используем 3D-модели (MeshInstance3D), а используем плоские изображения в 3D-мире.
  • Camera3D: Если мы хотим, чтобы камера следовала за игроком, её можно добавить прямо внутрь сцены игрока (или использовать отдельный RemoteTransform3D, но для начала хватит и вложенности).
  • Магия Sprite3D и Billboarding

    Главный секрет 2.5D эстетики кроется в настройках узла Sprite3D. Если вы просто добавите спрайт в 3D сцену, он будет лежать плоско или стоять как картонка. Когда камера вращается или смотрит под углом, иллюзия объема разрушается.

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

    !Сравнение полного Billboarding и Y-Billboard для 2.5D игр.

    В свойствах Sprite3D найдите раздел Flags -> Billboard и выберите Y-Billboard. Это заставит спрайт поворачиваться к камере только вокруг вертикальной оси Y. Это критически важно для RPG, так как персонаж должен оставаться вертикальным относительно пола, но всегда быть видимым для игрока.

    Также, для сохранения пиксель-арт стиля, в разделе Texture -> Filter выберите Nearest. Это отключит размытие пикселей.

    Векторная математика движения

    Передвижение в 3D пространстве требует понимания работы с векторами. В Godot ось Y направлена вверх, ось X — вправо, а ось Z — назад (на нас). В Top-Down игре персонаж движется по плоскости XZ.

    Получение ввода

    Вместо того чтобы писать четыре проверки if для каждой клавиши, мы используем мощный метод Input.get_vector(). Он автоматически обрабатывает нажатия клавиш (или стиков геймпада) и возвращает Vector2.

    Если игрок нажимает W (вперед) и D (вправо), input_dir будет равен вектору (примерно), так как метод get_vector по умолчанию нормализует диагональный ввод.

    Преобразование 2D ввода в 3D движение

    Нам нужно преобразовать двумерный вектор ввода в трехмерный вектор направления . Обратите внимание: y из ввода становится z в 3D мире, так как мы движемся по горизонтальной плоскости.

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

    Где: * — итоговый вектор направления в 3D пространстве. * — компонент X полученного ввода (влево/вправо). * — компонент Y, так как мы не хотим, чтобы персонаж летел вверх или проваливался вниз при ходьбе. * — компонент Y ввода, который становится глубиной (Z) в 3D (вперед/назад).

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

    Расчет скорости

    Итоговая скорость персонажа (velocity) рассчитывается как произведение нормализованного направления на скорость передвижения.

    Где: * — вектор скорости (Velocity), который мы передадим физическому движку. * — нормализованный вектор направления (длина равна 1). * — скалярная величина скорости (Speed), например, 5.0 метров в секунду.

    Реализация скрипта Player.gd

    Создайте скрипт player.gd и прикрепите его к узлу Player. Используем строгую типизацию, изученную в прошлом уроке.

    Разбор метода move_and_slide()

    В Godot 4 метод move_and_slide() не требует аргументов. Он автоматически использует свойство velocity самого узла CharacterBody3D и свойство delta (время кадра) для расчета перемещения.

    Самое важное — он обрабатывает столкновения. Если персонаж упрется в стену, move_and_slide() автоматически скорректирует вектор движения, позволяя персонажу скользить вдоль препятствия, а не застревать в нем. Это избавляет нас от сложной математики расчета коллизий вручную.

    Работа с гравитацией (опционально)

    Хотя в Top-Down RPG персонаж обычно не прыгает, добавление гравитации полезно, если в игре есть перепады высот (лестницы, холмы).

    Добавим простую гравитацию в _physics_process перед вызовом move_and_slide():

    Метод is_on_floor() возвращает true, если CharacterBody3D столкнулся с коллизией снизу в предыдущем кадре. Это работает только после вызова move_and_slide().

    Итог

    Мы создали базу для любого 2.5D проекта:

  • Настроили CharacterBody3D для физического взаимодействия.
  • Использовали Sprite3D с режимом Y-Billboard для визуализации.
  • Написали скрипт с использованием векторной математики для преобразования ввода игрока в движение в плоскости XZ.
  • В следующем уроке мы займемся анимацией спрайтов, создадим машину состояний (State Machine) для управления переходами между бегом и покоем, и научимся работать с AnimationPlayer в контексте 2.5D.

    3. Архитектура данных RPG: использование классов Resources и Dictionaries для инвентаря

    Архитектура данных RPG: использование классов Resources и Dictionaries для инвентаря

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

    Сегодня мы разберем профессиональный подход к архитектуре данных в Godot, используя классы Resource для создания базы предметов и Dictionary для управления инвентарем.

    Проблема узлов и решение через ресурсы

    Представьте, что у вас есть 100 видов мечей. Если создавать каждый меч как отдельную сцену с прикрепленным скриптом, где хранятся переменные damage, weight и cost, вы столкнетесь с проблемами:

  • Избыточность: Узлы (Nodes) содержат много лишней информации (позиция, поворот, физика), которая не нужна, пока предмет лежит в инвентаре.
  • Сложность редактирования: Чтобы изменить баланс, придется открывать сотни сцен.
  • Решение — Resource (Ресурс). Это класс данных, который не существует в дереве сцены, но может быть сохранен на диск как файл .tres. Ресурсы легковесны и идеально подходят для хранения характеристик.

    !Сравнение тяжеловесных Узлов и легковесных Ресурсов для хранения данных.

    Создание класса ItemData

    Давайте создадим базовый класс для любого предмета в нашей игре. Создайте новый скрипт item_data.gd. Обратите внимание, что он наследуется от Resource, а не от Node.

    Ключевое слово class_name ItemData позволяет нам создавать новые ресурсы этого типа прямо через контекстное меню редактора Godot, а также использовать ItemData как тип данных в других скриптах.

    Создание конкретных предметов

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

  • В панели FileSystem нажмите правой кнопкой мыши -> Create New... -> Resource.
  • В поиске введите ItemData (наш новый класс появится там).
  • Назовите файл, например, potion_health.tres.
  • В Инспекторе заполните поля: имя, описание, перетащите текстуру иконки.
  • Это и есть Data-Driven Design (дизайн, управляемый данными). Логика отделена от контента.

    Инвентарь на основе Dictionary

    Теперь нам нужно где-то хранить эти предметы. Самый очевидный выбор — массив (Array). Но массивы имеют недостатки при поиске. Если мы хотим узнать, есть ли у игрока "Зелье здоровья", нам придется перебирать весь массив.

    Для инвентаря отлично подходит Dictionary (Словарь). Это структура данных типа "Ключ-Значение".

    Почему Dictionary?

    В массиве доступ к элементу происходит по индексу (0, 1, 2). В словаре доступ происходит по ключу, которым может быть строка, число или даже сам объект ресурса.

    Структура нашего инвентаря будет такой: * Ключ: Ресурс предмета (ItemData). * Значение: Количество предметов (int).

    Реализация класса Inventory

    Создадим скрипт inventory.gd. Мы можем сделать его компонентом (Node), который прикрепляется к игроку.

    Использование Dictionary делает проверку наличия предмета (_content.has(item)) мгновенной, независимо от размера инвентаря.

    Математика веса инвентаря

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

    Общий вес инвентаря рассчитывается как сумма произведений веса каждого уникального предмета на его количество:

    Где: * — итоговый вес всех предметов в инвентаре. * — знак суммирования (сумма всех элементов последовательности). * — количество уникальных видов предметов в инвентаре. * — вес () конкретного вида предмета (берется из ItemData). * — количество () предметов этого вида (берется из значения в Dictionary). * — операция умножения.

    Реализуем эту формулу в коде нашего класса Inventory:

    Типизация словарей в GDScript 2.0

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

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

    Расширение: Drop Tables (Таблицы лута)

    Ресурсы полезны не только для предметов, но и для таблиц выпадения лута с врагов. Вместо того чтобы писать в коде врага if randf() < 0.5: drop_sword(), мы можем создать ресурс LootTable.

    !Логическая схема работы LootTable на основе ресурсов.

    Практическое задание

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

  • Создайте папку res://resources/items/.
  • Напишите скрипт item_data.gd и создайте 3 разных предмета (Зелье, Меч, Монета).
  • Создайте скрипт inventory.gd и прикрепите его к узлу Player как дочерний узел.
  • В скрипте игрока (player.gd) добавьте временный код в _ready(), который добавляет предметы в инвентарь и выводит в консоль общий вес.
  • В следующей статье мы займемся созданием графического интерфейса (UI) для нашего инвентаря, чтобы игрок мог видеть свои накопленные богатства, а не только читать о них в консоли отладки.

    4. Событийная модель: продвинутая работа с сигналами, await и RayCast3D

    Событийная модель: продвинутая работа с сигналами, await и RayCast3D

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

    Ответ кроется в событийной модели (Event-Driven Architecture) и пространственных запросах. В этой статье мы разберем работу с сигналами (Signal), корутинами (await) и «глазами» нашего персонажа — узлом RayCast3D.

    Философия сигналов: Decoupling

    В разработке игр золотым стандартом считается слабая связность (Decoupling). Это означает, что объекты должны знать друг о друге как можно меньше.

    Представьте ситуацию: персонаж бьет врага. * Плохой подход: В скрипте игрока мы пишем enemy.hp -= 10 и ui.update_health_bar(). Это создает жесткую зависимость. Если вы удалите UI или измените скрипт врага, код игрока сломается. * Хороший подход (Сигналы): Игрок просто кричит: «Я нанес удар!». Враг слышит это и получает урон. UI слышит это и обновляет полоску. Игроку все равно, кто его слушает.

    Объявление и вызов сигналов

    В GDScript сигналы объявляются ключевым словом signal. Они могут передавать аргументы.

    Подключение через код

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

    Асинхронность и await

    Одной из самых мощных возможностей GDScript 2.0 является ключевое слово await. Оно позволяет приостановить выполнение функции до тех пор, пока не произойдет определенное событие (сигнал), не блокируя при этом остальную игру.

    Это превращает функцию в корутину.

    Пример: Ожидание таймера

    Раньше для задержки приходилось создавать узел Timer и подключать его сигналы. Теперь это делается одной строкой:

    Пример: Ожидание сигнала

    Мы можем ждать любого сигнала. Это идеально подходит для пошаговых событий или катсцен.

    Взаимодействие с миром: RayCast3D

    Чтобы взаимодействовать с предметами в 2.5D RPG, нам нужно понимать, что находится перед игроком. Для этого используется RayCast3D (Луч).

    !Визуализация работы RayCast3D: луч исходит из центра персонажа и определяет объект взаимодействия.

    Математика луча

    Луч определяется начальной точкой и вектором направления. Конечная точка луча в мировом пространстве рассчитывается по формуле:

    Где: * — вектор конечной точки луча в мировом пространстве (куда луч попадает). * — вектор начальной позиции луча (обычно центр игрока). * — нормализованный вектор направления взгляда персонажа. * — скалярная величина длины луча (дистанция взаимодействия, например, 2 метра). * — операция умножения вектора на скаляр.

    В Godot узел RayCast3D упрощает эту задачу. Нам достаточно задать свойство target_position в локальных координатах.

    Настройка RayCast3D

  • Добавьте узел RayCast3D как дочерний к Player.
  • Установите Target Position (например, y = 0, z = -1.5). Это длина луча.
  • Важно: Включите галочку Enabled, иначе луч не будет работать.
  • Настройте Collision Mask. Обычно для интерактивных объектов создают отдельный слой физики (например, Layer 2: Interactables).
  • Скрипт взаимодействия

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

    Итог

    Мы создали надежную систему взаимодействия:

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

    5. Паттерны проектирования: реализация Конечного автомата (State Machine) для ИИ врагов

    Паттерны проектирования: реализация Конечного автомата (State Machine) для ИИ врагов

    В предыдущих статьях мы создали персонажа, настроили физику 2.5D мира и реализовали систему инвентаря. Теперь наш мир нуждается в обитателях. Создание искусственного интеллекта (ИИ) для врагов — одна из самых сложных задач в геймдеве.

    Новички часто пишут ИИ, используя бесконечные цепочки if-else в функции _physics_process. Это приводит к так называемому «спагетти-коду», который невозможно поддерживать. Если вы захотите добавить врагу новую способность, вам придется переписывать половину скрипта.

    Решение этой проблемы — паттерн Конечный автомат (Finite State Machine, FSM). В этой статье мы реализуем модульную систему состояний на продвинутом GDScript.

    Что такое Конечный автомат?

    Конечный автомат — это модель поведения, при которой объект может находиться только в одном из конечного числа состояний в любой момент времени. Объект переключается между состояниями при выполнении определенных условий (триггеров).

    Для нашего 2.5D RPG врага мы выделим три базовых состояния:

  • Idle (Покой): Враг стоит на месте и ждет.
  • Wander (Блуждание): Враг случайно перемещается в небольшом радиусе.
  • Chase (Преследование): Враг заметил игрока и бежит к нему.
  • !Диаграмма переходов состояний ИИ: от патрулирования к преследованию и обратно.

    Архитектура на основе классов

    В Godot FSM можно реализовать разными способами. Мы выберем Node-based подход с использованием классов. Каждое состояние будет отдельным узлом (Node) и отдельным скриптом. Это позволяет легко комбинировать поведение: одному врагу можно дать состояния Idle и Chase, а другому — Patrol и Shoot.

    1. Базовый класс состояния

    Создайте скрипт enemy_state.gd. Это будет абстрактный класс, от которого наследуются все остальные состояния. Мы используем class_name, чтобы типизировать наши переменные.

    2. Машина состояний (Мозг)

    Теперь создадим скрипт state_machine.gd. Этот скрипт будет управлять текущим активным состоянием и переключать их.

    Реализация конкретных состояний

    Теперь, когда каркас готов, мы можем наполнить его логикой. Предположим, у нас есть сцена врага Enemy.tscn (CharacterBody3D). Добавьте к нему узел Node с именем StateMachine и прикрепите скрипт state_machine.gd. Внутри StateMachine создайте узлы для состояний.

    Состояние Idle (Покой)

    Создайте скрипт enemy_idle.gd, наследуемый от EnemyState.

    Состояние Chase (Преследование) и Математика векторов

    В состоянии преследования нам нужно двигать врага к игроку. Для этого вспомним векторную алгебру из второго урока.

    Чтобы получить направление движения, мы вычитаем позицию врага из позиции цели:

    Где: * — вектор направления (не нормализованный). * — вектор позиции цели (игрока). * — вектор позиции врага.

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

    Где: * — единичный вектор направления (длина равна 1). * — длина исходного вектора (расстояние между объектами).

    Реализуем это в скрипте enemy_chase.gd:

    Преимущества такого подхода

  • Изоляция: Логика преследования находится только в EnemyChase. Если там есть баг, вы знаете, где искать.
  • Расширяемость: Хотите добавить состояние "Страх"? Просто создайте EnemyFear.gd и добавьте условие перехода в EnemyChase (например, если здоровье < 20%). Вам не нужно трогать код других состояний.
  • Повторное использование: Вы можете использовать одни и те же скрипты состояний для разных типов врагов, просто меняя параметры экспорта (скорость, радиус обзора) в Инспекторе.
  • Настройка в редакторе

  • Откройте сцену врага.
  • Добавьте узел Node и назовите его StateMachine.
  • Внутри него добавьте узлы Node с именами Idle, Chase.
  • Прикрепите к ним соответствующие скрипты.
  • В инспекторе StateMachine назначьте Initial State (перетащите узел Idle).
  • В инспекторе состояний назначьте ссылку на игрока (в реальном проекте это лучше делать динамически через get_tree().get_first_node_in_group("player") в методе _ready).
  • Теперь у вашего врага есть мозг! Он стоит, ждет, и если вы подойдете близко — начнет преследование. В следующей статье мы разберем систему боя и нанесение урона, используя эту архитектуру.