Хитрости и секреты, какие я применил за год соло-разработки, чтобы довести игру до релиза
Неочевидные приёмы, глупые решения, откуда и что стырил, скрытые алгоритмы, обманы перспективой и прочие улётные истории..
Приветствую, путник! Присаживайся.
Недавно я выпустил игру, где ты 95% времени беспечно ездишь на крутой красной железяке, пытаясь выжить, ..а остальное время мирно сидишь в офисе, чатишься с ребятами, да насмехаешься над никчёмными попаданцами, что разбились об очередной столб (по факту – над самим собой). Пока ты находишься на трассе – каждую минуту происходит что-то новое. А в офисах ты вершишь судьбами людей! (с иллюзией выбора, разумеется)
Эта статья – сборник маленьких, но занимательных историй, случившихся во время разработки этой игры. Длилась она около года (март/22 - февраль/23).
Статья подойдёт даже тем, кто в игру не играл / впервые слышит. Истории где-то неожиданные, где-то забавные, где-то поучительные (но это не точно). Устраивайся поудобнее, ведь прямо сейчас аэроцикл отправляется, чтобы сопроводить тебя в экскурсии по экзотическим событиям разработки ITRP _ Aero Star!
Аэроцикл на самом деле крохотный
Взгляните. Примерно так выглядит основной игровой процесс:
Иллюзия.
Вот как то же самое выглядит со стороны.
Машинка очень маленькая и находится очень близко к игровой камере.
Это исключительно техническая особенность, а не лорная. Аналогично как при моделировании разработчики моделируют лишь потенциально видимые игроком части объектов или же скрывают их, когда те перестают быть в поле зрения. По лору размер аэроцикла такой, каким он воспринимается на исходном видео.
Для того, чтобы иллюзия была менее явной – в самом начале игры аэроцикл показывают чутка издали, где он, казалось бы, выглядит как ему подобает. Однако конкретно для этой сцены он был многократно увеличен, чтобы в процессе плавно приобрести свой «истинный» размер.
Кто-нибудь из игравших смог заприметь здесь что-то неладное?
Специально для поддержки иллюзии даже пришлось отредактировать шейдер, добавив в него дополнительный множитель расстояния от камеры до точки в игровом мире, которую надо отрендерить. По умолчанию для всех объектов множитель = 1. Но лишь для аэроцикла = 8. Это нужно, чтобы при расчёте освещения маленький размер машинки не палился.
Тем не менее, размер-таки можно случайно спалить, если перемещаться внутри аэроцикла, пока он стоит на месте.
Я решил сделать именно так в первую очередь, чтобы уменьшить вероятность визуальных багов, когда аэроцикл мог бы проходить сквозь окружающие модели (дорога, препятствия, окружение). Это довольно компромиссное решение в такой ситуации.
Можно возразить, что схожего эффекта можно добиться, просто отрисовывая аэроцикл поверх всего остального мира, но такой подход накладывает определённые технические особенности, что могут негативно сказаться на производительности и которые следовало бы избегать.
Есть в этом подскоке что-то неправильное…
Ещё до того, как начать реализовывать прыжок, я переживал за его визуал. Будет ли он адекватно смотреться. Будет ли огрех бросаться в глаза.
Итог, на мой взгляд, получился хорошим. Но каковы же были причины моего беспокойства?
Возможно, пока аэроцикл не движется вперёд, огрех становится отчётливее.
Он заключается в том, что гравитация работает совершенно неправильно. Этому нет лорного объяснения и это не отсылка на мою игру про смену гравитации. Это просто условность, которую я себе позволил. Надеюсь, в глаза-таки не бросилась.
Чтобы создать музыку, я накладывал её на видео прохождения
Всю музыку к игре я сделал сам. И чтобы снизить шансы надоедания, я не хотел, чтобы она повторялась на протяжении всей 50-минутной трассы.
Приступил я к её разработке уже после того, как вся трасса была игромеханически готова (без учёта дальнейших правок). А перед этим записал видео полного собственного 50-минутного прохождения игры от начала до конца (честного, причём!). Закинул файл в видеоредактор и ориентируясь на то, что я там вижу на разных участках, пытался подбирать звучание, паттерны, настроение и прочее.
И каждый раз, как был готов какой-то вариант сэмпла, я закидывал его в редактор и на монтаже подставлял в нужные места, чтобы узнать как оно звучит, как попадает и надо ли что-то исправить.
Без повторений не обошлось. Дал слабину на некоторых участках, но в целом ~85% частей трассы имеет индивидуальный трек.
Только после того, как вся трасса была озвучена на монтаже, я начал интегрировать семплы в игру. Они должны включаться и переключаться в нужный момент и в целом вести себя корректно.
На то, чтобы подготовить всю музыку, я потратил месяц.
P.S. Основным источником вдохновения для звучания музыки этой игры у меня была музыка из Contra: Shattered Soldier от богоподобного Акиры Ямаока. Итог от источника вдохновения отличается, но вы можете чётко услышать схожести в отдельных моментах.
Вся игра – одна текстура
(почти)
Я довольно частенько этим промышляю: беру много всякой потенциально связанной визуальной информации и храню её в едином графическом буфере. Это и помогает сильно сократить количество файлов в сборке, и улучшает производительность, и частично уменьшает занимаемый объём памяти. Основной минус: нет возможности загрузить в игру лишь небольшой фрагмент рисунка без загрузки всего файла.
Практически всё, что показано на видео выше (аэроцикл, все дороги, все препятствия), хранится в одной текстуре.
Она не единственная, что используется в игре, но значительно превалирующая.
Помимо неё ещё есть: кликабельные мониторы; стекло; сцены в офисе; генерируемые текстуры. А у некоторых есть ещё и карты нормалей, что также фактически являются отдельными текстурами.
Пытаться ограничивать объём текстур – не приговор. Моя игра выглядит довольно бедной на визуал в первую очередь из-за ресурсных ограничений, в частности времени, которое я могу выделить на разработку. Игра была сделана за год, а чтобы добиться более визуально вылизанного результата, понадобилось бы куда больше времени, которое у меня нет возможности себе позволить.
В использовании единой текстуры немало преимуществ. Подходят ли они для создания той или иной игры, зависит от самого проекта, особенностей, нюансов, принципов, акцентов и т.д., но мне подошло.
Безусловно, частый реюз текстур – дело неблагородное, и в идеале хотелось, чтоб у каждого мимолежащего камня свои извилины были. Но также и не хочется сталкиваться с ситуациями, плата за которую значительно выше эмоционального выхлопа.
Не могу утверждать, что делаю грамотно, но уверен, что при креативном подходе можно реюзить текстуру так, что это будет выглядеть разнообразно, приятно и уместно.
Сбор кубика Рубика – это не ИИ, а очень запоминающий алгоритм
В одной из сцен в офисе, на столе лежит кубик Рубика. Изначально он был реализован так, что при нажатии просто поворачивался случайный его сегмент. И всё.
Сразу же первые несколько человек, кому я показал, остались раздосадованы тем, что его нельзя сложить назад.
Я это принял, но не стал ничего предпринимать, так как не планировал делать такую возможность. Это ж надо прорабатывать сложнющий алгоритм, что будет анализировать текущее положение, правильно подбирать дальнейшую комбинацию действий и у которого не должно быть права на ошибку. А сколько тестов нужно было бы провести и багов править – ужас!
Этот кубик был лишь в небольшой сцене и он не стоил того, чтобы тратить на него столько усилий. Потому я отбросил эту идею ..до тех пор пока спустя несколько дней не подумал просто втупую запомнить все действия при повороте каждого случайного сегмента и начать их отматывать в обратном порядке.
Так и получилось, что персонаж раскладывает кубик, а затем просто складывает его тем же самым путём назад.
Мыш! Кродеться
Раз уж пошли по офисным предметам – в одной из сцен есть милая игрушечная мышка. Если её завести – она проделает небольшое путешествие с одной части стола до другой.
Вот тут можно рассмотреть модель мыши вблизи и со всех сторон.
Потратили на неё весь вечер, но оно того стоило!
Пространство за офисным окном – обычное мыло
Один из элементов, который должен подчеркнуть, что разные сцены в офисах лорно являются разными помещениями – окно. Хоть за ним и постоянно что-то мутное и невразумительное – тем не менее, можно определить, что каждый раз там происходит что-то разное.
В этой области находятся 2 текстуры.
Одна из них полупрозрачная – стекло. Позволяет увидеть разводы и прочие пятна и линии, что помогает идентифицировать эту область как стеклянную.
Другая – внешнее пространство. Незамысловатая низкокачественная картинка 64x128 пикселей.
Видимая область движется по горизонтали. А при попадании в офис выбирается случайная координата по вертикали, которая будет удерживаться при движении.
Данное место – тот случай, где целенаправленно была сделана текстура с низким разрешением.
Потребовалось немало времени, чтобы всё нарисовать и закодить так, чтобы имитировать некоторую естественность по отношению к визуалу прочих объектов в офисе. Тем не менее, результат вы видели.
Осколки аннигилируются при высокой нагрузке
Вернёмся на трассу.
При попадании аэроцикла в препятствие оно разлетается на осколки.
Осколки сильно бьют по рендерингу. Порой он начинает требовать на столько больше времени, что некоторые компьютеры начинают не вывозить стабильные 60 FPS.Для этого прописал небольшой алгоритм, который по длительности рендеринга в рамках нескольких последовательно идущих кадров рассчитывает «уровень» нагрузки. И в зависимости от него безанимационно аннигилирует часть осколков, пока ситуация не станет приемлемой.
Если нагрузка не прекращается – игра автоматически перейдёт в режим 30 FPS для того, чтобы синхронизировать игровое время и реальное. Также автоматически вернётся режим 60 FPS, сразу же когда нагрузка упадёт. Это помогает удерживать темп, в то время как отсутствие переходов между режимами рассинхронизировало бы время при просадках. Для большей плавности рассматриваю потом добавить режимы на 20, 40 и 120 FPS, которые также будут переключаться в зависимости от нагрузки. Но если это произойдёт, то очень не скоро.
Поездка по трубе – фейк
Речь о вот этих трубах.
В действительности аэроцикл всегда находится в одной и той же точке (без учёта езды вперёд). Вместо него вращается и двигается всё окружение, всевозможные предметы вокруг.
На видео ниже можно увидеть, как мир выглядит со стороны. На 15-ой секунде вокруг начинают появляться статические предметы, что удерживаются в одной точке виртуального пространства. Это поможет понять поведение трассы и препятствий.
Такой трюк был сделан для простой синхронизации между сегментами трассы.
Когда впервые попадаешь на трубу, аэроцикл оказывается в конкретной точке. Поскольку он в ней удерживается, переход на следующий сегмент всегда случится в одном и том же месте, а не в случайной точке под случайным углом, как это могло быть, если бы аэроцикл перемещался «честно».
Как маленькое преимущество – расчёт столкновений стал быстрее, так как техническое положение аэроцикла в пространстве в расчётах не принимает участия.
Чтобы имитировать естественное движение, пробовал делать небольшие сдвиги камеры внутри аэроцикла при «перемещении», как это есть во всей остальной игре. Но возникли технические трудности, а позже я понял, что трачу на это больше времени, чем могу на это дело уделить, и оставил как есть.
Планировался эффектный полёт среди осколков
В последней четверти игры планировал и даже пытался реализовать полёт раздробленных осколков рядом с аэроциклом.
Соответственно, чем больше ударяешься, тем больше осколков рядом с тобой летает. Их можно было бы и задержать на подольше.
Эффектно, но физически некорректно?
Так и есть, потому как альтернитву допускал сделать такое на сегменте с трубой.
Поскольку ты передвигаешься по внешней её части – можно было сделать так, словно ты падаешь вниз, а не едешь вперёд. Тогда эффектный разлёт был бы физически обоснован. Но из-за того, что там двигается окружение, а не аэроцикл (см. блок выше), осколки бы при движении не меняли бы позиции – а это смотрелось бы дурно. Исходный вариант был также отброшен в целом из-за: физической нелогичности; нагрузкой на рендеринг; ещё одной нагрузкой на расчёт физики осколков, по умолчанию её нет, а им не следует проходить сквозь препятствия.
Как идиот разбивал каждое препятствие вручную
Последнее, что расскажу об осколках – у каждой модели препятствия есть альтернативная модель, что разбита на осколки. И разбивал я их 3D-редактором вручную.
Больше года назад подробнее показывал это в твиттере – можете глянуть, как это происходило. Выглядит завораживающе.
Ручками я разбивал только саму плоскость, а всякие «утолщения» и прочие модификации делал кодом. Написал отдельную программу для быстрой обработки осколков, которая в самой игре не использовалась.
Полёт на святом духе
Тестировщики жаловались: на этапах с рельсами было часто тяжело сориентироваться, когда можно прыгать на соседнюю рельсу.
Это периодически приводило к несрабатываемым нажатиям.
Потому я решил изменить код так, чтобы учитывалась не только текущая точка, но и точка, что лежит впереди на 80% одного пролёта. Благодаря этому рельса будет считаться доступной, даже если она не рядом. Теперь персонаж буквально каждый раз может часть времени ехать по пустоте. Зато играть стало проще.
Правда это сломало смысл некоторых участков трасс. Из-за того, что я упростил передвижение по рельсам, часть мониторов стала бесполезными. Но в релизной версии они всё равно там есть.
Железнодорожные призраки
На рельсах был сделан один из ультимативнейших трюков.
Одна из тех деталей, которая не планировалась, но которая смогла ощутимо облегчить преодоление игры практически всем, кто сюда добрался.
Она скрыта от глаз, должна помогать фоном, а игрок не должен был о ней подозревать.
Ведь если ты о ней не знаешь, ты ощущаешь опасность, которая рассеивается, как только о ней узнаёшь.
Я надеюсь, она выручила многих.
И надеюсь, что не была никем замечена ранее.
Слабый обзор при нахождении на верхней рельсе хоть и подразумевался как дополнительное усложнение заезда, но во время тестирования это не понравилось никому.
Как следствие – безмерное количество ударов об препятствия, которые многими воспринимались нечестными.
Наверное, в подобных играх нет ничего хуже, чем урон, полученный незаслуженно.
Сказать, что после введения в игру этой особенности, она стала играться менее напряжённо, а тестеры стали меньше жаловаться – сказать недостаточно.
Вот этот кусок я действительно стырил из Battletoads
Пока я занимался разработкой этого этапа трассы, мне просто вспомнился данный фрагмент из игры про боевых лягух, и я захотел сделать также. Это даже не ироничное заявление.
В обе части Battletoads играл в детстве и не раз проходил до конца в юношестве на эмуляторе.
Между словом, уровень на байках вообще несложный. Он длится-то всего 2 с половиной минуты, да и возможных действий немного. Там дальше есть уровень с крысой – вот он куда хуже.
Нет никаких физических моделей
Частенько при рисовании внутриигровых объектов в 3D-редакторах делают не только визуальную модель, но и физическую. Вторая не участвует в рендеринге, а просто используется для расчёта коллизий.
Подобный подход способен чрезмерно упростить задачу при разработке, но тянет за собой определённые особенности.
Aero Star достаточно предрасположена к тому, чтобы можно было безболезненно отказаться от использования физических моделей. Все расчёты столкновений аэроцикла об дорогу и препятствия происходят с помощью достаточно примитивной математики. Таким образом и игру реализовать проще, и процессору жить куда легче, и расчёты быстрее проходят, и памяти меньше занимает, и процессы под бóльшим контролем, и за текстуры не выпадешь из-за того, что треугольники случайно вывалились.
Такой вид повреждений позволяет увидеть затекстурье
Я об этом узнал практически сразу как реализовал. Но из-за особенностей расчёта разновидностей близлежащих сегментов дороги избавиться от этого почти нереально без дополнительных расчётных нагрузок, которые отрабатывались бы всегда, но в 99,99% излишне бы нагружали процессор. Оно того не стоило, и я сделал просто пару лёгких хитростей. При ударе на 1-2 кадра можно увидеть затекстурье, но аэроцикл сразу отбросит немного назад, чтобы хоть как-то скрыть это. Результат не идеальный, но в целом работоспособный.
Некоторые идеи придумались из-за багов
Например эта:
Или вот эта:
И всякое по мелочи вроде этого:
Имитация околосверхвысоких препятствий
Когда игрок попадает на ту часть трассы, которую я называю «дно», он видит препятствия, что устремляются вверх.
Это обычные высокие препятствия, но у которых искусственно кодом удаляется шляпка, труба трижды размножается, а шейдер часть зачерняет.
Босс был придуман из-за череды случайностей
На одном сегменте трассы вдали появляются устрашающие глаза.
Чтобы пройти этот сегмент, используется механика закрывания глаз. Хоть сам сегмент в итоговом виде был придуман довольно поздно – его история начинается издревне. В ней многое могло пойти не так, что привело бы к тому, что этих свисающих глаз бы в игре вовсе не было. И сейчас я вам эту историю поведаю.
Ещё до того, как начать что-либо строить руками, я обдумывал то, как игровые механики будут лорно объяснены. Я хотел, чтобы каждое возможное действие имело обоснование с т.з. внутрилорных правил. Сижу да расписываю в блокноте поведение рычагов, поведение трассы и т.д.
В итоге я добился обоснования каждому действию, кроме дистанционного «клика» по мониторам.
Я рассматривал вариант наличия кнопки в аэроцикле, на которую персонаж нажимает, когда хочет использовать монитор вдали. Но ведь его руки постоянно заняты и находятся на рычагах.. Может добавить кнопки прямо на рычаги? Как раз потом нужны будут дополнительные действия «влево» и «вправо». А тут, как удачно, 2 рычага: левый и правый. Вроде логично. Но есть другая проблема..
Монитор выбирается с помощью направления взгляда персонажа. Чем это обосновать?
Может рядом с глазами персонажа находится лазерный прицел, который попадает по этим мониторам, тем самым связываясь с ними?
Такое объяснение мне понравилось, но.. как связать лазерное устройство, прикреплённое к виску, с кнопками на рычагах? Как они обмениваются данными? Wi-fi? Длинный провод, что болтается под ногами? Лор подразумевает существование и активное использование беспроводной связи между устройствами, но в этом случае я не хотел к ней прибегать. Я остановился на интерфейсной независимости лазерного девайса, которое отныне.. управляется закрыванием глаз. Как и рычагов, есть левый, есть правый.
Можно было придумать много чего, но выбрал этот вариант в некоторой степени из безнадёжности, хоть мне он и нравится. Следуя такому плану, я приступил к реализации. Сделал некоторые механики. Сделал часть трассы. Дошёл до момента, когда появляется механика с глазами.. Её тоже сделал.. Построил часть трассы под эту механику. Расставил мониторов. Поиграл. И только потом до меня дошло, что вообще-то, если персонаж кликает по мониторам глазами – он не должен ничего видеть, если оба глаза закрыты (игрок зажал на клавиатуре обе кнопки, отвечающие за один и другой глаз).
Исключительно ради того, чтобы логика не была нарушена, добавил затемнение половины экрана, если один глаз долго закрыт, и резкое затемнение, если закрыты оба глаза.
Общая картина игры начала становиться менее целостной. Ведь если игра реагирует на действие игрока/персонажа вот так – оно в идеале должно быть для чего-то нужно и должно быть где-то используемо. А так это просто особенность, которая просто есть и всё.
Хоть это и тревожило меня, я оставил этот нюанс как есть, в надежде, что он не будет игроками принят в штыки. В конце концов в играх часто различные элементы присутствуют только, чтобы что-то подчеркнуть.
А потом случилось неладное. Однажды, играясь и тестируя некоторые элементы игры, я просто так закрыл глаза.. подождал.. а когда открыл, увидел.. /*барабанная дробь*/.. обычную трассу, ровно то, что и должен был увидеть. ..Но именно в этот момент вместе с открытием глаз ко мне молниеносно пришла мысль а-ля «вот было бы круто, если б я увидел что-то иное».
Так и начала появляться идея добавить в середину игры момент, где это будет использоваться. Он должен был быть точечным. Ты длительно и неистово уворачиваешься от массы препятствий, спокойно закрываешь глаза и, выждав нужное количество времени, открываешь ..и видишь пустоту..
Таким и должен был быть этот момент. Но случилась последняя проблема.
Это нельзя было просто так взять и с ничего бросить в игрока. Нужно было к нему грамотно подвести, чтобы игрок был готов. Чтобы он понимал ситуацию и знал, чего от него хочет игра. Так и появился этот босс. Он плавно «объяснял» игроку правила. Он был сделан из-за крохотного финального фрагмента, а в итоге превратился в отдельную небольшую секцию.
Он был придуман благодаря стечению обстоятельств. Тут многое могло пойти иначе, но случилось, как повелось. По-моему, это прекрасно.
Это первая половина всех тех историй, которые я хотел рассказать о разработке игры. Я прям ну очень рассчитывал уместить всё в одну статью, чтобы закрыть этот вопрос, но всего оказалось так много, что я принял сложное для себя решение, которое не принимал ещё никогда за всю историю написания статей на DTF, – разделить статью на 2 части.
Сложное оно потому, что стараюсь для целостности делать по одной статье на одну тему. Но полагаю, дорогой путник, что ты уже немного устал и тебе нужно отдохнуть от всех этих гонок.
Ну и ещё сложное потому, что я собирался закликбейтить заголовок статьи, упомянув там о своих боданиях с сотрудниками Valve, но из-за того, что эти истории будут во второй половине, придётся этим закликбейтить следующую статью. Она выйдет послезавтра. В среду. А если не буду успевать – отредактирую эту, впишу сюда другой день и скажу, что так и было.
Успехов тебе, путник!