#5 Godot: 3D Platformer от BornCG. Добавление монетки на уровень
А мы двигаемся дальше.
Сегодня рассмотрим:
- регистрацию столкновений;
- удаление объектов, с которыми сталкивается игровой персонаж.
Вот как выглядит наш проект на текущий момент:
На предыдущем уроке мы добавили на нашу сцену Level1 спрайт Icon и анимировали его путём добавления функции вращения.
Решать поставленные задачи мы будем путём создания объектов, с которыми наш игровой персонаж сможет взаимодействовать - иными словами, монеток. У этих монеток есть несколько особенностей:
- они являются переиспользуемыми объектами, то есть нам нужно будет создать одну монетку, экземпляры которой мы затем разместим на уровне;
- монетки вращаются (потому что могут);
- монетки исчезают после их касания игровым персонажем.
Что ж, приступим. Создаём новую вкладку в рабочем пространстве и добавляем корневой узел Area3D, который является областью, обнаруживающей входящие в неё объекты.
Предсказуемо переименуем корневой узел Area3D в Coin.
Мы видим жёлтый треугольник предупреждения возле корневого узла Coin - так как узел Area3D является областью в пространстве, ему нужна форма. Ну и меш, естественно, тоже.
Добавляем дочерний узел MeshInstance3D, в Инспекторе в свойстве Mesh выбираем Создать CylinderMesh.
Внимательный читатель может заметить, что цилиндр не особо похож на монетку. Это поправимо: в Инспекторе в свойстве Mesh кликаем на картинку цилиндра, чтобы раскрыть выпадающий список настроек этого самого цилиндра.
Выставляем значения Top Radius и Bottom Radius на 0.3, а значение Height меняем на 0.15. В результате у нас из цилиндра получается замечательная шайба.
Кроме того, предполагается, что монетки будут висеть в воздухе на ребре, чтобы студенты могли наконец подготовиться к сессии. Поэтому в рабочем пространстве берёмся за полукруг вращения возле модели монетки, зажимаем Ctrl для дискретности и поворачиваем монетку вокруг оси Z на 90 градусов.
Теперь дело за формой: добавляем дочерний узел CollisionShape3D, в Инспекторе в свойстве Shape выбираем Создать CylinderShape3D, далее кликаем на эту строчку и в раскрывающемся списке выставляем значение Height, равное 0.15, и значение Radius, равное 0.3.
Затем с помощью мышки и клавиши Ctrl поворачиваем CollisionShape3D вокруг оси Z до полного совпадения с моделью монетки, то есть на 90 градусов.
Заметим, что для упрощения вычислений и в целях понижения требований к ресурсам компьютера мы могли узлу CollisionShape3D задать форму не цилиндра, а куба, выбрав в Инспекторе в свойстве Shape опцию Создать BoxShape3D.
Сохраняем результат.
Пришло время повертеть нашу монетку. Обратите внимание, что код мы добавляем именно в сцену coin.tscn, содержащую нашу монетку - это необходимо для того, чтобы все экземпляры нашей монетки, которые мы будем добавлять в другую сцену (спойлер, но это будет сцена level_1.tscn), имели одинаковые свойства, в данном случае - одинаково крутились вокруг своей оси.
Итак, добавляем файл кода в сцену coin.tscn, все настройки оставляем по умолчанию.
По аналогии с предыдущим уроком добавляем в код константу ROT_SPEED для хранения числа градусов угла, на которые монетка должна поворачиваться каждый кадр (которых у нас по умолчанию 60 в секунду), а в функцию _process() добавляем функцию вращения rotate_y(), в которой размещаем функцию для перевода градусов в радианы deg_to_rad(), в которой размещаем константу ROT_SPEED.
И не забываем про комментарии.
Переходим во вкладку level_1.tscn, к корневому узлу Level1 инстанцируем файл coin.tscn и размещаем монетку где-нибудь не в плоскости пола.
Посмотрим, что получилось.
Монетка есть, монетка вращается, но не исчезает при касании. Значит, пора это дело исправлять.
Задач состоит в следующем: при касании игровым персонажем монетки последняя должна удалить сама себя из уровня. За проверку на касание с другим объектом отвечает функция has_overlapping_bodies(), а за удаление объекта из игры - функция queue_free(). Тогда мы можем изменить функцию _process() следующим образом:
(Queue означает выстраивание в очередь, потому что удаляемый объект буквально встаёт в очередь на удаление. При удалении объекта происходит освобождение памяти, выделяемой для этого объекта - поэтому у функции queue_free() есть приставка free. Само удаление проходит за один кадр, то есть за один проход функции _process(), что при 60 FPS займёт 1/60 секунды.)
Посмотрим, что у нас получилось в итоге.
Вроде неплохо, но если присмотреться, видно, что исчезновение монетки происходит с некоторой задержкой, за которую наш игровой персонаж успевает немного пройти сквозь модель монетки. Всё дело в функции has_overlapping_bodies(), которая для производительности обновляет список перекрытий один раз за кадр и перед шагом физики, что и приводит к некоторой задержке.
Придётся отказаться от использования функции has_overlapping_bodies(). Вместо неё мы будем использовать сигналы, которые позволяют собственноручно прописывать нужную реакцию объекта при наступлении тех или иных событий, и которые работают эффективнее дефолтных функций.
Если мы в сцене coin.tscn в панели Сцена выберем корневой узел Coin...
а затем в панели Инспектор перейдём на вкладку Узел, то увидим список сигналов, доступных для обработки.
Из всего списка нас интересует сигнал body_entered, который вызывается в случае вхождения некоего body в область Area3D. Дважды кликнем по этому сигналу, чтобы открыть окно автоматического добавления функции, обрабатывающей данный сигнал, в код (конкретно - в самый низ кода).
После нажатия кнопки Присоединить нас перекидывает на файл с кодом coin.gd, и мы видим перемены к лучшему:
Кроме того, в панели Сцена у корневого узла Coin появилась иконка быстрого доступа на панель сигналов.
Если кликнуть на неё, автоматически откроется вкладка Узел в панели Инспектор, где так же отмечен факт наличия обработки сигнала.
Ну и нам остаётся только добавить в обработчик сигнала функцию для удаления объекта в случае пересечения области другим объектом. И да, предыдущий код, использующий функцию has_overlapping_bodies(), нужно закомментировать, чтобы он не влиял на игровой процесс (можно выделить эти две строчки и нажать Ctrl+K).
Вот что у нас получается с кодом в итоге:
Вот как оно выглядит:
И вот как оно играется:
Я долго писал этот текст, но на характере получилось довести его до конца. На самом деле, после душной предыдущей части с изучением языка возможность вернуться к реальной работе над проектом добавляет оптимизма и желания продолжать. Надеюсь, у вас также.