DefBall (codename). Два месяца разработки

Title 05.png

Всем привет в очередной раз!

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

capture-devlog-creation-v3.gif

Где гифки, Лебовски?

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

Начнём с того, что я немного подрастерял энтузиазм, и процесс разработки перестал быть чем-то естественным и самим собой разумеющимся. Вдобавок, задумавшись о низком качестве HTML5-билдов, я стал подумывать о том, чтобы издаваться на компах, а не в вебе, и совсем потерял определённость: куда двигаться, чем заниматься дальше. Отсутствие определённости — неприятная штука, и позже мы об этом ещё поговорим.

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

Наконец, переезд на новую квартиру и разнообразные новоселья отняли достаточно времени и сил. Я больше не был погружен в работу над игрой, а ещё и застрял на этапе подготовки текста для девлога. Две недели куда-то пропали, и это обидно.

Нефичекат

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

  • Подбираемые ресурсы.
    • Определить, из кого сколько вываливается. Нарисовать.
  • Скиллы.
    • Придумать. Привязать к коду. Нарисовать иконки. Определить стоимость в ресурсах.
  • Сюжет.
    • Катсцены с чатом. Туториал, раскрывающий детали мира и сюжета. Привязка сюжета к заданиям (или наоборот). Продумать цель героев, метод достижения, с какими трудностями приходится сталкиваться.
  • Саунд-дизайн.
    • Стрельба из пушек. Взрыв. Молния. Горение. Чат. UI.
  • Музыка.
  • Другие фичи для разнообразия геймплея.
    • Лазер.
    • Гравитационные ловушки.
    • Молния-стена.
    • Спаунеры.
    • Боссы.
    • Блобы взлома.
    • Быстрые враги.
    • Объединяющиеся враги (сливаются в один блоб или в босса).
    • Мембраны.

Но затем я посмотрел на пункты «Подбираемые ресурсы», «Скиллы» и «Саунд-дизайн» и спросил себя: успею ли я за месяц сделать хотя бы их? Нет, вряд ли. Если и успею, то с большой натяжкой. Надо было устроить фичекат.

И всё бы ничего, только фичекат получился каким-то неправильным. Вместо того, чтобы просто взять и выпилить всё ненужное, оставив однозначно нужное, я сделал нечто противоположное.

Дело было в том, что я стал сомневаться: а под ту ли платформу создаётся игра? Четвёртый Godot не умеет экспортировать качественные HTML5-билды, значит, мне стоит делать оффлайновую игру для ПК? Потом придётся искать издателя, а издателю будет нужен более-менее приличный продукт: обычная поделка здесь не прокатит. Вот как-то так потихоньку в моей голове казуальный проект и превратился в мидкорный. Заметка от 16 мая хорошо иллюстрирует мои метания:

Теперь я не знаю, резать ли сюжет. И что ещё можно отрезать, чтобы успеть за два месяца.

Изначально сюжета я не планировал. Я поиграл в «Боевую кнопку» и подумал: здорово сделать похожую игру, но без кликов.

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

И тут же добавил:

А потом был Алавар [читай: просто какой-нибудь издатель] и мысль о том, что мне нужно сделать круто. Я подумал, что в моей игре должна быть вещь, которая будет удачно выделять её среди остальных.

Раз не знаю, выпиливать ли сюжет, давай распишем плюсы и минусы.

Плюс. Сюжет сделает игру интереснее. Если он хороший. Это простая игра, но сюжет увеличит её ценность. Он будет являться клиффхэнгером, заставляющим игрока играть дальше.

Минус. Думаю, написание истории и диалогов займёт недели две. Реализация чата и катсцен — ещё неделю. Может, я и растянул сроки, но думается, что не особо.

Ох и люблю же я спорить с собой! Некоторое время спустя, но в тот же день, я написал:

…сюжет почти не нужен, и можно обойтись либо совсем без него, либо парой катсцен с чатом.

  • это тестовая, черновая версия;
  • пока что можно сделать очень просто, главное чтобы было играбельно и чтобы самому нравилось:
    • чтобы было ощущение, что это не просто тестовая сцена, а завершённая игра, логически оформленная;
    • возможно, чтобы была глубина, которая была в «Кнопке» за счёт метапрокачки;
  • если что, сюжет я сделаю в следующей версии.

И ведь здравые были мысли! Делай ты как можешь, а если время останется, то и остальное приложится. Но не тут-то было. Я сначала продолжил в том же духе, а потом свернул не туда:

Может быть, мне ещё не дают покоя мои гештальты. Например, я замечаю, что в моих играх не хватает контента, арта, стиля или чего-то ещё. Тот же задник помог, и с ним игра выглядит куда лучше, хоть он и тестовый. И так во всём. Визуально всё как-то слишком просто, плоско, не так вкусно, как хотелось бы.

Ещё наличие уровней. Вдруг можно обойтись без них? Но из-за того, что раньше мне всё никак не удавалось добавить множество уровней в игру (а разнообразие уровней — это как-никак контент), сейчас это обстоятельство незавершённости преследует меня. То есть хочется уже наконец сделать полноценную игру. Не какую-нибудь, а именно полноценную.

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

В тот вечер я достал старую тетрадку, в которой пишу и рисую всякое по играм и не только, и потратил пару часов на это:

Notes - Skills.jpg

Notes - Biohacker.jpg

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

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

Легко писать наперёд, выдавая желаемое за действительное. Спустя неделю я, вопреки «сделанным заранее» выводам, испытывал и ощущение безделия, и баттхёрт от отсутствия прогресса. Да, я слегка передохнул от кодинга, но когда за неделю на экране монитора ничего нового не появляется, начинаешь задумываться о стагнации.

Когда-то здесь был сюжет

Чего только не нароешь у себя на компе, копаясь в старых исходниках. Я всё-таки рад, что уселся за этот девлог; ведь именно сегодня, в день написания текста, я нашёл, пожалуй, самое раннее описание сюжета ДефБолла:

Простейшая история. Два человека, один слепой, другой немой. Играем за слепого. Событие одно. В место, где они живут, приносят человека без сознания, слепой «подключается» к нему — начинается геймплей.

Внедрение — это какой-то процесс. Процесс использует микроскопический зонд, который помещается в какую-то часть тела человека.

(Можно рассказать обо всём очень поверхностно. Например, о процессе внедрения зонда можно рассказать только то, что берётся человек, в него помещается зонд, и этот зонд необходимо защищать какое-то время — до того, как он выполнит свою работу. Ну и ещё нужно рассказать о том, для чего это делается — тоже только в общем плане.)

А вот следующая фраза главного героя, несмотря на некоторую наивность,

Я уже давно не видел внедрения своими собственными глазами. Я и позабыл, как оно выглядит на самом деле. Наблюдая за звуками в этой комнате, я мог лишь представлять, как Том кладёт безжизненное тело на стол, как открывает чемоданчик с инструментами, как достаёт оттуда шприц и баллончик с дезинфектором, как обеззараживает затылок нашего нового гостя… Дальше была только тишина, нарушаемая биением моего сердца, и моё воображение заиграло на полную катушку. Вот игла входит под его кожу, вот зеленеют его вены, вот зонд, окружённый мириадой «пустышек», достигает его точки Кью. Атака на инородную жидкость начнётся сразу же, но зонд не будет атакован, агенты не рассмотрят его среди «пустышек», по крайней мере, до тех пор, пока он не дойдёт до цели. Щёлк! Это Том включил тумблер, направив сигнал от зонда к моему зрительному интерфейсу. Началось. Теперь дело только за мной.

Это первое описание внедрения. Для чего нужен этот процесс? Возможно, в этом и состоит прелесть — в том, что я и сам пока не знаю. Не знают и игроки. Именно эту абстракцию и нужно им передать; и всё же хорошо, когда абстракция обёрнута в какую-то видимую форму, в то, что можно наблюдать снаружи — а что за этим стоит… Пусть игроки пока придумывают сами.

Глядя на гифки из предыдущего поста, вы могли подумать, что в 2016 году DefBall был игрой про шарик, отбивавшийся от других шариков при помощи шариков. Как бы не так: мы играли за живого человека, который, скрываясь от властей, живёт в оборудованном подземелье. Он потерял зрение во время инцидента, учинённого тоталитарной правительственной организацией, и теперь способен «видеть» только то, что передаётся в его зрительный интерфейс посредством других устройств — цифровых камер или других источников видеосигнала.

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

Вот ещё одна цитата из того же старого документа:

Надо определить цель внедрения — для чего оно делается? Чтобы добыть из «пациента» что-то, но что именно? […]

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

И несколько тезисов

  • Линкер — это глобальная система авторизации.
  • Инфильтрация — процесс взлома Паспорта, зашитого в голову агента, с помощью Зонда. Биочип паспорта пытается себя защитить, рассылая биороботов, атакующих Зонд. Уровень защиты Паспорта зависит от уровня агента.
  • С помощью данных, полученных при взломе Паспорта, можно авторизоваться в Линкере, а затем и в правительственной сети.

С тех пор некоторые детали поменялись (в частности из-за того, что были забыты), но основная идея осталась той же: авторитарное государство, враждебные агенты, Подполье, взлом биологической защиты.

Когда-нибудь здесь будет сюжет

Подумал я. И уселся за его придумывание.

Результаты работы за первый вечер я уже приводил. На всякий случай, вот расшифровка записей, дополненных с телефона:

Что можно развивать.

  • Максимальную массу или максимальный радиус зонда.
  • Плотность зонда. Приводит к увеличению массы.
  • Запас маны.
  • Радиус основной пушки.
  • Скорострельность основной пушки.
  • Материал снарядов основной пушки:
    • большая плотность, а значит, масса и прямой урон;
    • разрывные;
    • поджигающие;
    • замораживающие;
    • коэффициент отталкивания снарядов.
  • Возможность делать композитные снаряды, то есть использовать несколько материалов, а значит, и веток прокачки.
    • Одна ветка группы (например, разрывная или воспламеняющая) лочится, а скилл композитных снарядов даёт возможность разлочить одну ветку.
  • Прокачивать отдельные пушки?
    • Общая огневая мощь.
      • Материалы для формирования снарядов передаются в зонд по главному каналу.
      • Из переданных первичных материалов в огневом модуле формируется промежуточный материал для снарядов.
      • Формирование происходит «на месте», ближе к каналу, принимающему композит и передающему его к пушке.
      • Первичные материалы обладают разной химической чистотой. (Можно приобретать разные модули для очистки и улучшать её степень.)
      • Разные техпроцессы требуют разных первичных материалов. В результате получаются разные типы композитов, обладающие разными:
        • shrink speed;
        • density;
        • direct damage coefficient;
        • velocity affect coefficient;
        • explosion probability and def;
        • lightning probability and def;
        • burn probability and def.
    • Итак, повторим.
      • Первичные материалы подаются через основной канал к резервуарам, расположенным вокруг огневого модуля.
      • Первичные материалы из резервуаров подаются к конвейерам огневого модуля, где и происходит сборка композитного материала для снарядов.
      • Набор конвейеров зависит от типа техпроцесса. Разные техпроцессы обладают совершенно различными наборами конвейеров.
      • Промежуточный композит нестабилен, для его поддержания в пригодном для использования виде расходуются дополнительные ресурсы огневого модуля. Композит необходимо как можно скорее передать к резервуарам пушек.
      • Передача композитов к резервуарам пушек происходит по тонким каналам. Каналы имеют сложное устройство, позволяющее поддерживать композит в рабочем состоянии, и, как следствие, имеют ограниченную пропускную способность. Они могут изготавливаться по разным техпроцессам и из разных материалов.
      • Резервуар пушек представляет собой расширение в области канала. Кроме того, он снабжён дополнительными функциями, позволяющими формировать готовый снаряд и придавать ему необходимое ускорение.
      • Некоторые типы снарядов (например, разрывные) крайне чувствительны к высоким ускорениям; для их стабилизации могут применяться дополнительные средства. Но в общем целом они проигрывают в скорости обычным снарядам.
      • Пропускная способность основного канала вряд ли будет чем-то ограничена.
  • Симулятор биовзломщика?
    • Нет сквозного сюжета.
  • Каждое задание / уровень — заказ от какого-то клиента.
  • UI в виде рабочего стола?
    • Задания в виде отдельных папок.
  • В разговоре с клиентами можно раскрывать мир.
    • Например, клиент-нуб будет просить помощи в подготовке взлома, главный герой будет ему дистанционно объяснять: найди порт, он выглядит вот так (кидает картинку в чат), вставь туда иглу с зондом.
  • Дистанционные миссии — только один из типов миссий. Главный герой пользуется чужим зондом, но управляет им дистанционно.
    • Могут служить источником денег, но не прокачки.
  • Инъекции снотворного: опциональны, но облегчают прохождение.
    • Стоят денег.
    • Можно получить, взломав медицинский кейс.
    • Количество полученного зависит от навыка «Убеждение».
  • Генерируемые (несюжетные) квесты.
    • Случайный объект взлома.
    • Служат источником денег (гринд для приобретения инъекций).
  • Апгрейды стоят не только денег, но и материалов.
  • Главный герой в прошлом потерпел неудачу, и теперь ему приходится начинать с нуля.
    • Поначалу приходится консультировать; но это первый источник доходов.
    • Это же и основной источник информации для игрока.

Как видим, в какой-то момент мне захотелось изменить концепцию игры:

не взламываем агента, а выполняем заказы, генерируемые случайным образом.

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

Говорят, каждый сценарист должен знать о своих персонажах больше, чем зритель, посмотревший фильм. Я не совсем гуманитарий, поэтому с придумыванием персонажей у меня беда; вместо них мне хочется прорабатывать что-нибудь другое. Например, из головы не лез вопрос: каким образом устроена биологическая защита, для чего она предназначена и в каких случаях вообще может быть полезна? Пытаясь ответить на него, я определил для себя несколько деталей, которыми поделюсь здесь, но в их применимости я до сих пор сомневаюсь.

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

Такая клетка, в свою очередь, могла бы содержать в себе биологический ключ — например, последовательный набор белков, похожий на тот, что заключён в молекуле ДНК, только гораздо меньший по размеру. Последовательность белков в ключе зависит от ДНК заражённой клетки; таким образом, подсаженный вирус выполняет роль хеш-функции, принимающей на вход молекулу ДНК и возвращающей биологический ключ. В теле одного и того же человека вирус всегда будет создавать один и тот же биоключ (или почти один и тот же). Ключи двух разных людей с высокой степенью вероятности будут различаться.

Если затем этот биологический ключ поднести к похожему биологическому замку, белки в ключе и в замке начнут взаимодействовать. От того, какие белки участвуют во взаимодействии, зависит конечный результат процесса. Например, у обоих белков, состоящих в паре, может измениться фолдинг, и это приведёт в действие связанный с ним химический механизм. Либо, если белки ключа и замка «не подходят» друг к другу, ничего не произойдёт, и ключ не сработает.

This is Sci-Fi.png

Эту схему можно перевести в несколько больший масштаб, отказавшись от белков и заменив их клетками. Клетки являются более сложной структурой, чем белки, они более разнообразны, и на их основе можно создавать совсем уж изощрённые механизмы. Более того, чем тоньше система, тем сильнее она подвержена влиянию негативных факторов: даже репликация ДНК, отлаженная в течение миллиардов лет эволюции, происходит с ошибками, чего уж тогда говорить о рукотворном ключе на основе тех же белков. Прибавим сюда другие факторы вроде космических лучей и придём к выводу, что наш микроскопический ключ не будет работать без ошибок.

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

Однако всё это хорошо на бумаге и в теории. Когда я задумался о других вещах, начались трудности. Биоключ встроен в тело носителя, значит, то же можно сказать и о биозамке? Если биозамок будет встроен в железо, а не в плоть, каким образом он будет функционировать? А если нужно произвести аутентификацию дистанционно, от человека к железке, то каков будет интерфейс? В теле человека будет железный порт вроде джека на 3,5 или USB, только меньший по размеру? Получалось, что биоключ годится только для встраиваемого аппаратного обеспечения, и последнее должно производиться специально под биологический ключ. Например, система идентификации личности в воображаемом тоталитарном государстве могла бы выглядеть так: в тело вшивается радиомодуль, обеспечивающий соединение с сетью, вокруг него выращивается биологический ключ, и радиомодуль не работает в отрыве от ключа. Как-то слишком сложно, если учесть, что в сети личность всё равно будет представлена тем же цифровым айдишником, состоящим из нулей и единиц. Почему бы тогда не использовать обычный RFID?

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

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

Реальная разработка

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

Quote 01 p1.png

Импакт при попадании снаряда по врагу — вроде бы, просто. Но как сделать мини-взрывчик при уничтожении уже почти сдувшегося противника? Будет ли взрыв, распространяющийся из маленького шарика, выглядеть естественно? Я пока не стал ломать над этим голову и решил внедрить то, для чего у меня уже был задел — подсветку (как в каком-нибудь Metal Slug):

capture-metal-slug-v2.gif

Подсветка была сделана с помощью свойства modulate класса CanvasItem. Я уже использовал его для назначения прозрачности блобам; оставалось только дописать утилитный класс ColorMod для более удобной анимации цвета. Когда снаряд попадает по врагу, тот подсвечивается, а затем подсветка плавно спадает:

capture-001-12-00.gif

С подсветкой всё смотрелось чуть интереснее, но не хватало каких-то других украшений — например, осколков. В оригинальном ДефБолле и Аиде стрельба выглядела интересно из-за того, что снаряды раскалывали врагов на части. Каждый отколовшийся кусок также являлся врагом, и иногда это оказывало негативное влияние на производительность. Сейчас мне хотелось избежать этого, поэтому осколки было решено унаследовать от обычных спрайтов, а их положение рассчитывать вручную в обход физического движка.

capture-001-13-00-v1-32colors.gif

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

Подсветки и осколков мне казалось недостаточно, и я добавил искры. Писались они наспех и поначалу никак не хотели работать из-за досадного бага. Вот первый исправный вариант:

capture-001-15-00.gif

Затем я взялся за улучшения: добавил разброс по количеству искр, их направлению, начальному положению, начальной скорости, времени жизни и цветовой фазе. Каждая искра стала рисоваться в режиме наложения ADD, поэтому осветляла пиксели, которые под ней лежат. Все перечисленные настройки для создания искр были объединены в один вспомогательный класс SparkParams, так что теперь я мог использовать разные параметры для каждого конкретного случая. Например, при обычном попадании во врага искры отскакивали с небольшим разбросом по направлению, а при уничтожении врага разброс увеличивался до 360 градусов — искры разлетались во все стороны. Вопрос с мини-взрывчиком решился сам собой: его роль играл своеобразный салют, получавшийся при схлопывании блоба.

capture-001-17-00.gif

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

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

Verlet 01.png

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

Euler 01.png

После этого искры заработали как надо.

capture-001-18-00-v1.gif

Linear Damp versus Gliding

Кто бы мог подумать, что linear_damp, предлагаемый Годотом из коробки, не работает одинаково на разной частоте кадров? Я вот не мог. Поэтому «немного расстроился», когда попробовал сменить частоту кадров в настройках проекта и увидел, что мои снаряды начинают вести себя по-другому. Погуглив изрядно, я наткнулся на обсуждение проблемы в issue tracker’е документации:

it’s actually implemented as a negative exponential damper, which behaves very differently (and poorly, because this implementation is fully tickrate dependent)

Следовательно, надо было отказываться от linear_damp и вводить свой метод расчёта скорости. Благо, Godot предоставляет нам такую возможность посредством _integrate_forces().

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

velocity_1 = start_velocity * gliding

После двух апдейтов она примет значение

velocity_2 = velocity_1 * gliding = start_velocity * pow (gliding, 2)

после трёх апдейтов — значение

velocity_3 = velocity_2 * gliding = start_velocity * pow (gliding, 3)

В общем случае, после n апдейтов скорость будет пропорциональна n-й степени gliding:

velocity_n = start_velocity * pow (gliding, n)

Когда частота кадров непостоянна, мы используем похожий метод. Сначала мы задаём стандартное время кадра (скажем, 1/60 секунды) и относительно него определяем gliding объекта. Теперь коэффициент скольжения будет показывать, во сколько раз увеличивается скорость объекта по прошествии одного стандартного кадра. Во время обновления мы определяем, сколько стандартных кадров прошло с момента предыдущего обновления, пересчитываем текущую скорость и находим силу сопротивления среды:

Gliding 01.png

Тут всё точно так же: за один стандартный кадр скорость будет умножена на gliding в первой степени, за два стандартных кадра — на gliding в квадрате, за полтора стандартных кадра — на gliding в степени 1,5. И так далее.

Анимация цвета осколков

Осколки — это хорошо, но по сравнению с аидовскими они всё равно смотрелись как-то не так. Из-за разницы в цвете создавалось ощущение, что они изначально не принадлежат исходному блобу, а являются чем-то отдельным. Может, это и хорошо, ведь игрок должен понимать, где враг, а где — безвредный объект. Но в Аиде враги были того же цвета, что и их осколки, поэтому казалось, что блоб распадается на части; в текущем проекте такого эффекта не наблюдалось. Я подумал, что неплохо было бы заставить осколки плавно менять цвет, чтобы сначала они были похожи на своего предка, а через некоторое время становились тёмно-серыми. Вот что получилось в итоге:

capture-001-21-00.gif

Мне нравилось то, что я видел. С импактами было почти покончено, и можно было переходить на следующую версию.

Version Info.

  • Location uses background texture.
  • Guns' and Bullets' Explosions and Lightnings became configurable via Def objects.
  • Effects were implemented as a feature. The Stun and Burn effects were added.
  • Custom particle system for the Burn Effect was implemented with cacheable HSLA-gradient.
  • When an Enemy is hit by a Bullet:
    • the Enemy is highlighted;
    • one or more Splits break off from the Enemy;
    • a group of Sparks fly from the contact point.
  • Color quartets used to describe blob’s appearance are organized into T01_BlobColor class. The Blob now uses a T01_BlobMaterial instance instead.
  • The _drag field utilizing Godot’s linear_damp under the hood was replaced with the _gliding field. The latter is used in hand-rolled calculations of drag force.
  • The minimum and maximum radii of a Probe are visualized now.
  • Direct damage coefficient is used now by Enemies and Bullets.
  • Enemies deal damage to player’s Probe.
  • Explosion now stuns the Enemies.

Рефакторинг и продолжение работы над импактами

К этому моменту меня изрядно замучили префиксы T01_ в названиях классов, и мне захотелось выйти из теста #01 и переписать всё «на чистовик». Поэтому работу над версией v0.0.3 я начал с рефакторинга. Мне казалось, это займёт уйму времени, но я управился меньше чем за полдня.

Refactoring 01.png

Как это приятно — пользоваться красивыми именами классов!

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

capture-002-04-00.gif

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

capture-002-05-00.gif

Получилось скучновато, но я оставил всё как есть. Возможно, позже я сделаю что-то вроде кровотечения, чтобы попадание по игроку выглядело более драматично.

Наконец, я добрался до следующего пожелания:

Quote 01 p2.png

Тряску камеры мне уже доводилось делать для конкурсной LFM, но тогда я реализовал её достаточно примитивно: каждый апдейт просто смещал камеру относительно центрального положения на случайное значение.

capture-camera-shake-v1.gif

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

  • сначала выбирается направление действия силы и амплитуда толчка (амплитуда лежит в каком-то диапазоне);
  • затем за время полупериода модуль силы увеличивается от нуля до текущей амплитуды (сила возрастает линейно);
  • в течение следующего полупериода модуль силы возвращается к нулю.

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

На объект действуют три силы: сила толчка, сила тяготения к центральному положению и сила сопротивления.

  • Сила толчка описана выше.
  • Сила тяготения к центру является силой упругости: она прямо пропорциональна расстоянию от объекта до центрального положения.
  • Сила сопротивления действует как демпфер и рассчитывается аналогично тому, как это делается в классе блоба или искры (на основе параметра gliding).

С самого начала я понаделал багов и весь вечер с ними боролся, но в итоге всё заработало так, как я и ожидал. За счёт того, что влияние на положение камеры происходило опосредованно, с помощью ускорений, тряска получилась плавной и потому реалистичной; с другой стороны, чувствовались и мелкие толчки, действовавшие с интервалом в 60 миллисекунд (3 — 4 кадра при 60 FPS) и затухавшие в течение 400 миллисекунд.

capture-002-08-00-(3).gif

Была ещё одна вещь, которую предложил сделать E1e5en — плавный поворот зонда за указателем мыши:

Quote 02.png

Решено было ввести три области.

  • Мёртвая зона — небольшой кружок в центре зонда, радиус которого пропорционален радиусу смерти. Когда указатель мыши находится в мёртвой зоне, его положение не влияет на целевой угол поворота зонда;
  • Область мягкого поворота, находящаяся между внешней границей зонда и мёртвой зоной. Внутри этой области поворот происходит с задержкой (на основе параметра, аналогичного коэффициенту скольжения). Чем ближе указатель мыши к мёртвой зоне, тем медленнее поворот.
  • Область мгновенного поворота, лежащая за пределами зонда. Когда указатель мыши находится над этой областью, зонд поворачивается в его сторону без какой-либо задержки.

capture-002-09-00.gif

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

И это всё

По-хорошему мне следовало бы раскрыть ещё две темы — несостоявшийся переезд на Godot 3.5 и разработку системы скиллов. Но результаты работы по ним всё равно не видны, а текст и так получился довольно объёмным; признаться, он меня уже достал — с начала написания первой строчки прошло недели три, а я его всё никак не доделаю. Значит, пора остановиться и опубликовать то, что есть. Хотя бы для галочки.

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

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

https://drive.google.com/file/d/1ZXP6kpFiwWvjkme3Sqnp0Tsq91tp2cEi/view?usp=sharing

  • Управление: стрельба — левая кнопка мыши, взрыв — пробел.
  • В главном меню кнопка «Continue» начинает новую игру (сохранение не реализовано).

Играйте, оценивайте и делитесь мнениями в комментариях. Не зря же на моё нытьё добрая психологичная нейросеть отвечает:

Neural 01.png

До следующей встречи!

  • dump
  • 08 июля 2024, 01:53