Godot и сферический диаблоид в вакууме
Итак, я собрал прототип arpg-проекта Сферамида с теми механиками, о которых писал ранее прошлой в статье Поиграть в браузере/скачать можно здесь: https://thenonsense.itch.io/spheramyd
Ниже некоторые подробности, по игре и приёмам работы с движком Godot.
Особенности игровой структуры и механик
Сферические подземелья
В то время как с разбиением цельной сферы на единообразные, удобные в текстурировании элементы, имеются некоторые проблемы, сбор сферического уровня из отдельных «комнаток» частично решает этот вопрос. То есть вместо того чтобы замостить всю сферу единообразными элементами на 100%, мы составляем сеть «комнат» и «переходов», оставляя часть пространства неиспользуемым. Остаётся лишь более менее аккуратно стыковать «комнатки» друг с другом. Кстати, даже цельную сферу в любом случае стоило резать на отдельные части из соображений оптимизации, а «комнатный» подход этому изначально лучше соответствует.
С точки зрения текстурирования «комнаты» есть цельные, пол и стены единой моделью с одной текстурой, а есть разделённые, где у пола отдельная тайловая трипланарно наложенная текстура.
Потолки сделаны отдельными моделями, и по задумке должны показываться только те их них, которые находятся перед игроком (исключая тот, что над ним). Пока что отрисовываются все, что попали в зону видимость камеры. Планируется сделать коллайдер, который будет включать нужные.
Персонаж
В отличие от первой моей «сферической» игры Relight, где управление было «танковое» — здесь персонаж двигается в 8 фиксированных направлениях, благодаря чему его можно лучше рассмотреть, вид вокруг не так быстро меняется и в целом подход более диаблоподобный.
Предполагается, что когда персонаж погибает, то игра продолжается за другого, ранее найденного, пока есть запас таких персонажей, как это было реализовано в прототипе Chaosborn На данный момент в игре один уровень и перерождений не происходит.
Концентрированное здоровье
Герой может найти по 3 вида бутылочек маны и здоровья. Под использование каждой отведена отдельная кнопка в интерфейсе, срабатывающая пока есть зелья такого типа.
У шкал здоровья и маны есть параметр концентрации, который падает со временем. Простое зелье не даёт концентрации. Среднее — меньше ресурса, но и некоторое количество концентрации. Сильное — ещё меньше ресурса, но больше концентрации.
При низкой концентрации шкалы зелье восстанавливает ресурсы с задержкой. При средней концентрации — сразу. А при высокой — шкала постоянно регенерирует, пока концентрация не понизится до среднего значения.
Органический инвентарь
Разные предметы блокируют разное количество пустых окружающих ячеек, попадая в инвентарь. Если же стараться укладывать предметы вплотную, то можно уменьшить количество заблокированных клеток, высвободив больше места. Зелья не имеют блокирующего влияния.
Камни заклинаний
Отдельный вид предметов, которые могут лежать в инвентаре или в специальных слотах, где комбинации камней в верхней строке определяют доступные герою заклинания. Первый камень и второй устанавливают заклинание на левую кнопку мыши, а второй камень с третьим дают заклинание на правую кнопку мыши. На данный момент комбинации дают одно из 4-х заклинаний.
Кнопка действия
Предметы подбираются с пола автоматически, но для взаимодействия с некоторыми объектами требуется нажать кнопку E — сейчас это пустые капсулы-саркофаги. Также можно открывать двери, а также закрывать их. Для этого требуется зажать кнопку действия, тогда после некоторой задержки будет вызван метод открытия у последней обнаруженной поблизости двери.
Враги
На данный момент имеется 3 вида противников — слизни, призраки и призраки-маги. Враги на уровне изначально пребывают в дезактивированном состоянии, но вокруг персонажа существует специальная большая зона, которая «размораживает» врагов при касании, или «замораживает» их обратно, если они вышли из её области действия.
Слизни просто путешествуют в том направлении, куда смотрят. Сталкиваясь с препятствиями меняют направление, а коснувшись игрока наносят ему повреждения.
Призраки мониторят окружающее пространство вращающимися рейкастами и, если обнаружат игрока — двигаются к нему. Если персонаж попадает в зону атаки, то призрак атакует. Призраки маги ведут себя похожим образом, но вместо ближних атак создают выстрелы и приближаются медленнее.
На самом деле призраки устроены таким образом, что в режиме обнаружения персонажа вращается сам их центр, расположенный в центре сферы уровня. Это нужно, чтобы враг разворачивался точно в сторону персонажа в момент попадания рейкаста. Для того, чтобы призраки не вертелись визуально вокруг своей оси, я сделал, чтобы сама моделька призрака компенсировала вращение центра, вращаясь в противоположную сторону.
Скрипты, алгоритмы, логика
Способы связи с объектами
Несмотря на наличие в Godot сигналов, чаще всего используются сигналы встроенные, вроде добавления функции от события нажатой кнопки. Использовать самописные сигналы обычно не требуются, если только не идёт речь о том, что постоянно через код в сцене будет возникать что-то динамическое, требующее прикрепления к себе сигнала. Но даже в этом случае можно вместо сигнала просто передавать создаваемому объекту ссылку на родителя или другой управляющий скрипт, к которому тот может обращаться, вызывая какие-то методы.
Что касается получения получения ссылок на фиксированные объекты сцены, для дальнейшего манипулирования ими, то здесь имеется два основных ходовых варианта — прописать путь к узлу в инициализации скрипта или прописать там же поля экспорта, в которые можно будет скинуть ссылки на эти узлы в окне редактора.
Те объекты, которые сами имеют на себе скрипты, могут при первом появлении в сцене отправлять ссылку на себя в глобальный синглтон. Естественно, если там заведены соответствующие поля, изначально обозначенные как null. Например, это может сделать сам главный скрипт, чтобы прочие могли обращаться к его методам. Также удобно использовать такой тип создания ссылок для множественных единообразных элементов имеющих скрипт, например, для кучи однотипных кнопок. То есть для каждой кнопки через экспорт в окно редактора выводится её порядковый номер, а при первом появлении на сцене скрипт кнопки отправляет ссылку на кнопку в глобальный синглтон, в массив на позицию с её номером. Теперь можно иметь доступ к кнопкам, например, из главного скрипта — допустим, перерисовать их текст, при нажатии на смену языка в настройках.
Узлы-обёртки
В большинстве случаев удобнее крепить отдельные специфические элементы (модель, спрайт, кнопка, текстовое поле) к простому стандартному узлу, который будет выступать в качестве контейнера. Это очень пригодится, например, при создании анимаций средствами движка — вместо того, чтобы непосредственно анимировать какую-то модель (MeshInstance или префаб), анимируется узел-контейнер модели. Таким образом, если потребуется сменить модельки, то можно будет не переделывать анимацию заново, а подкорректировать уже имеющуюся. То есть вместо конкретной анимации «молоток ударяет по гвоздю», делается анимация вида «пустышка ударяет по пустышке», где в первую вложен «молоток», а во вторую «гвоздь». И позднее можно вложить в них другие модели, например, «топор» и «полено» (или заменить «молоток» и «гвоздь» высокодетализированными версиями), без пересоздания анимационного трека заново.
Анимация-таймер
Можно использовать пустые анимации в качестве более наглядной замены таймеров. Например, в игре при некоторых действиях персонажа в его внутреннем аниматоре, внутри префаба модельки, проигрывается нужный трек. Чтобы не заводить скрипты внутри префаба, создавать визуальный таймер или лишние внутренние счётчики — создаётся специальный аниматор с пустыми треками, который запускается параллельно с анимациями персонажа. После проигрывания трека этот аниматор отправляет сигнал в главный скрипт. В пустую анимацию можно со временем добавить и какие-то дополнительные эффекты, не трогая префаб и анимации самого героя.
Защита от множественного срабатывания
Иногда требуется по условию сделать с объектом что-то важное, необратимое, и только один раз. Например, удалить врага, у которого кончилось здоровье от полученного выстрела. Для гарантии того, что операция не произойдёт несколько раз (в том числе пытаясь удалить то, чего уже нет) желательно использовать в объекте какой-то регулирующий это флаг. Например, у монстра может быть переменная is_exist, изначально истинная. Когда монстр должен умереть, то это происходит только при успешной проверке is_exist на истинность. Тогда сначала is_exist устанавливается в ложное положение и далее происходит операция удаления. Теперь из тысячи одновременно прилетевших во врага снарядов, уничтожит его только один из них.
Часто используемые практики
Создание и импорт моделей
Для моделирования используется Blender. Собирается моделька с модификатором Mirror, разворачивается, красится. Затем сохраняется в отдельный файл, где отзеркаливание применяется и модель крепится к скелету. После этого создаются отдельные файлы с разными анимациями.
Простые модели, без анимаций, я перекидываю в Godot в формате .obj, которые затем следует открывать внутри движка, устанавливая как форму MeshInstance. Нужно убедиться, что у экспортируемой модельки порядок с нормалями, применены модификаторы, есть развёртка (если надо) и экспортируется только она, ничего лишнего и скрытого. Также стоит удалить с неё материал, чтобы собрать его заново уже в Godot. При экспорте также может появиться файл .mtl, он не нужен (тем не менее, более старые версии Godot могут писать об ошибке с .mtl при закидывании .obj файла, но на это можно не обращать внимания).
Импорт анимированного персонажа
Объекты с костной анимацией я экспортирую из Blender в Godot в формате collada (.dae). Это не то, чтобы рекомендуемый вариант, но он работает. Материал с модели также удаляется, а вот модификатор арматуры применять не нужно.
После того как файл перенесён в Godot, нужно щёлкнуть по нему, открыть вкладку Import (слева вверху), в разделе Animation — Storage выбрать пункт «Files (.anim)» и нажать внизу кнопку reimport.
Далее модель добавляется на сцену и нужно открыть её для редактирования (выбрав пункт new inherited). Там, в аниматоре (AnimationPlayer) меняем имя дефолтной анимации на желаемое название анимации и сохраняем эту анимацию как файл .anim. Также сохраняем саму открытую сцену персонажа как файл .tscn — это и будет префаб с моделькой.
Теперь убираем со сцены модельку в формате .dae, и добавляем/используем тот полученный .tscn префаб. Сам файл .dae продолжаем хранить в ресурсах.
Добавление новых анимаций персонажу
Имеется в виду случай, когда у нас есть файл с тем же самым скелетом (возможно даже без самой модели), но с другой анимацией, которую хочется добавить в список анимаций ранее добавленного героя.
Порядок действий аналогичен импорту анимированного персонажа, с тем отличием, что после сохранения дефолтной анимации как файла .anim, закрываем открытую сцену персонажа без сохранения. Далее открываем префаб с персонажем и в аниматоре выбираем пункт Load, чтобы загрузить только что созданный .anim к списку анимаций персонажа.
Удаляем со сцены добавленную .dae, из которой взяли новую анимацию. Сохраняем сцену, после чего удаляем эту .dae и из ресурсов — после сохранения её анимации она больше не понадобится.
Полезное
Фишки среды разработки
В Godot можно нажать Ctrl и кликнуть по строке кода с вызовом функции, чтобы переместиться в то место, где находится эта функция. Таким же образом, кликая по переменным можно перемещаться в место, где эта переменная объявляется. Выделенный код можно двигать вверх-вниз стрелками клавиатуры. Чтобы закомментировать несколько выделенных строк сразу нужно нажать кнопку K, а для раскомментирования Ctrl + K.
Эффект просвечивания
Для того, чтобы силуэт героя подсвечивался, когда его перекрывает препятствие, нужно сделать вот что: у базового материала героя приоритет рендера сменить с 0 на 1, а в next pass добавляется второй материал с нужным цветом и флагами unshaded и no depth test.
Спасибо за внимание.