Проектный вторник #2
Сегодня вторник - это время рассказать, над чем я работал целую неделю. В фокусе был проект Zzzz-Zzzz-Zzzz. Я уже писал здесь про разработку второй части этой игры и даже есть демка.
Увы, пришлось вернуться к первой части "франшизы". Сейчас расскажу почему и какие у меня успехи на этом поприще за прошедшую неделю.
Это игра про сны. Персонаж застрял в мире снов и пытается проснуться, слоняясь из одного сна в другой. В игре невозможно проиграть, каждый сон - это ситуация, которая имеет множество решений, приводящих к переходу в новые сны. Поначалу вы будете просто бесцельно бродить, пытаясь попасть в новые сны и разблокировать все кровати. Но в какой-то момент вы поймёте что нужно сделать, чтобы игру завершить.
Я сделал эту игру в 2013-ом году, ровно 10 лет назад. Сперва, в мае 2013-го года была создана конкурсная версия игры. А потом в течение июня-июля того же 2013-го была сделана полная версия. Сейчас, спустя десять лет, по случайному стечению обстоятельств, в июне я берусь снова делаю игру под современные технические стандарты.
Зачем?
Если вы запустите игру сейчас - она откроется в небольшом окне. В ней нет поддержки геймпада. А в Стим-версии нету ачивок, хотя в самой игре ачивки внутри есть. Это я и собираюсь всё исправить. Для чего, казалось бы? Готовлю игру для издателей.
Почему я не делал этого до сих пор и почему откладывал? О да, сейчас я подробно расскажу, что значит переносить проект с Game Maker 8.1 на Game Maker Studio 2.
Исходные данные
На самом деле я уже занимался этим проектом. Лет 6 назад я хотел сделать расширенную версию игры с удвоенным контентом. И довольно много сделал по ней. Но пришлось отложить проект, да и весь свой геймдев на пару лет из-за IRL обстоятельств. Важно отметить, что проект я портировал тогда на GMS2, и там есть несколько вещей, которые я планирую перетянуть в текущий порт.
А ещё я делал вторую часть этой игры буквально два месяца назад. Там тоже есть любопытные наработки, которые я внедрю в текущий порт.
Сразу условлюсь с терминологией в рамках данного поста и для сокращённости буду называть:
Game Maker 8.x как GM8
Game Maker Studio 1.x как GMS1 или просто GMS
Game Maker Studio 2.x как GMS2 или "вторая студия". Это касается и самой последней версии движка, хоть его и называют теперь снова просто Game Maker, Но по сути и функционалу это GMS2.
Расширенная версия - это так и не выпущенная версия игры Zzzz-Zzzz-Zzzz с удвоенным контентом. Она портирована на GMS2.
Вторая часть - это демо-версия продолжения игры, которую я сделал в апреле этого года.
Основные проблемы
Технически, исходник с GM8 на GMS2 протащить нет никаких проблем. Сперва проект импортируется в GMS1, а затем уже во вторую студию. Это я сделал. Но игра при этом сходу будет вряд ли работать. По крайней мере эта. Довольно много отличий между версиями движка. Часть функций устарела, часть инструментов была переделана.
Вот перечень основных критических изменений:
Полностью изменена работа с audio
Изменена работа с камерами
Слияние фонов и спрайтов в один вид ресурсов (спрайты)
Полностью переделана тайловая система
Полностью переделан редактор комнат, система слоёв
После импорта проекта я имею базу, с которой нужно провести довольно много манипуляций, чтобы игра заработала.
Прогресс: Аудио
Первое, что я сделал после импорта проекта - это занялся звуками и музыкой. Старая версия игры использовала библиотеку SuperSoundSystem.dll, которая была очень популярна в те бородатые годы среди геймекерводов. Был ещё FMOD, но его подключать было сложнее. Я почти всегда использовал именно SuperSoundSystem.dll, что обязывало хранить звуки и музыку во внешних файлах. Сейчас встроенная система GMS2 работает не хуже этой библиотеки, а для более простой работы с ней нужно эти звуки и музыку внести в проект в качестве ассетов.
И тут мне как раз помогли мои прошлые наработки, т.к. всю эту работу я уже проделал для расширенной версии. Я быстро импортировал ассеты и заменил начинку API функций, чтобы они работали не с dll, а со встроенными аудио функциями.
Прогресс: Камера
Это второе, что нужно было починить. В GM8 вся работа с камерой сводилась к использованию функций вида (view - это вид внутри сцены по терминологии Game Maker). В GMS2 всю эту систему зачем-то разбили на камеры и виды. Теперь камера - это видимая область внутри сцены, а вид - это размер окна игры. Вот вам фиговые объяснения из справки GMS2
В случае с 2Д играми, камера и вид - это одно и тоже. А вот view port - это как раз размер отображаемой области на экране игрока. Движок сам скейлит камеру под размер этоой области. Если view port будет в 2 раза больше вида камеры, то будет масштаб x3.
Это всё относительная ерунда и при импорте проекта IDE сама создаёт нужные скрипты и производит автозамену на костыли от разработчиков движка. Оно всё работает, конечно, но жутко неудобно.
Проблема камеры в первой Zzzz-Zzzz-Zzzz носит концептуальный характер. Все сцены затачивались под размер экрана. Про адаптив в 2013 году мало кто из инди знал и заморачивался этим. В те года стандартом были 720p и 1024p. Поэтому окно игры 800x600 не выглядело так убого, как сейчас на FullHD или QHD мониторах. Итого, чтобы сделать разрешение хотя бы на FullHD - нужно поменять соотношение сторон, а значит - дать в уровнях пространство по бокам. Вариант "оставить там пустые дырки" меня решительно не устраивает. Когда я смог запустить игру, увидел справа пустое место , которое показывает излишек при переходе от 4:3 к 16:9
На самом деле эту проблему я решал ещё во второй части игры, т.к. в неё планировалось сделать часть уровней из первой Zzzz. Старые уровни либо чётко вертикальные, либо чётко горизонтальные, либо прямоугольные. Так что мне нужно просто делать комнату больше по размеру и лочить камеру по осям X-Y для вертикальных и горизонтальных уровней. В прямоугольных уровнях камера и так следовала за игроком. Одноэкранные уровни я просто чуток дорисую и прибью камеру гвоздями к нужным пикселям.
Но это только половина задачи. Вторая половина - это расчёт размеров самой камеры. Дело в том, что в пиксельарте я перфекционист. Никаких повёрнутых или смасштабированных в дробях пикселей я не приёмлю. Только чёткий апскейл по иксам. Для чего ещё в Холодной Тишине я выработал вот такое решение:
Задаётся условный делитель высоты, что-то близкое к желаемой высоте камеры, но на самом деле это просто число, которое выбирается подгоном.
Высота окна/экрана делится на эту желаемую высоту и получается коэффициент масштаба. Дробный. Округляем его до верхней границы, т.к. функцией ceil.
Реальная высота камеры - это высота окна/экрана делённая на этот коэффициент . Исходя из соотношения сторон окна/экрана рассчитывается и ширина.
Тут есть небольшой подвох. Реальная высота камеры может плавать. Я немного опростоволосился с этим во второй части игры. Обычно я делитель из первого пункта выбирал на глаз, думая что это будет примерно высота вида, но хрен там плавал. На реальных типовых разрешениях там может быть неплохая такая погрешность. Это я понял, когда составил вот такую таблицу расчётов:
Немного поиграв с делителем я пришёл к вот этому:
Видно, что актуальная высота принимает всего три возможных значения: 180, 192 и 190 пикселей. Это идеальный вариант в моём случае, т.к. разрешение в исходнике первой версии стоит 288x192. То есть мне нужно только предусмотреть сверху и снизу зону в 8 неприкосновенных пикселей. Только в тех уровнях, где залочено вертикальное движение камеры.
Все эти расчёты были проведены два месяца назад и сразу же перекочевали в порт.
Прогресс: Шрифты
Давно я использовал в проекте шрифт Hooge, который настолько геометрический, что успешно косит под пиксельартовый. Со временем я стал использовать свои кастомные шрифты, собранные из условного атласа. Это позволяет их быстро и просто добивать спецсимволами при переводах на всякие языки типа испанского, немецкого или китайского.
Замену шрифтов я уже проделывал для расширенной версии игры, поэтому тут всё просто - перенёс спрайты-атласы и заменил использование шрифтов через макросы. Но все не так просто как хотелось бы.
Часть букв в шрифте стали скомкаными. Приглядевшись, я понял что это касается только широких букв - они масштабируются так, чтобы по ширине быть как обычная буква. Сперва я подумал что нужно сделать что-то с настройками шрифта. А потом вспомнил, что у меня была подобная проблема при работе над расширенной версией. Связана она с багом в специфической функции отрисовки шрифта. Я её использовал для рисования повёрнутого текста в двух местах за всю игру. Поменял на обычную функцию, которая работает хорошо, но придётся выкручиваться как-то теперь в этих двух местах.
Прогресс: Хранение данных
При первом же запуске мне вывалилась куча ошибок об использовании устаревших функций. В основном, это было в скрипте загрузки игры.
Формат сохранения - дичь, с использованием аналога eval из javascript. Слава богам, всю рефлексию выпилили ещё из GMS1, так что вопрос настолько банальных уязвимостей отвалился.
Но пришлось переделывать. Это в любом случае пришлось бы переделывать, но я планировал заняться этим чуть позже. Проблемы нет, у меня есть готовые наработки по универсальной системе сохранения так, чтобы она была совместима со всеми современными консолями. Эти наработки я и использовал для этого, переделав всё за один вечер.
Прогресс: Уровни
Как я уже говорил, редактор сцен претерпел множество изменений. Из-за чего при импорте старых проектов создаётся тонна костылей.
В старых версиях Game Maker сцена делилась только на: фоны, объекты, тайлы. Для них настраивался параметр depth, который по сути отвечает за сортировку отрисовки. В Game Maker Studio 2 этот параметр был упразднён, а не смену пришли слои, которые могут быть разных типов. Отдельно слои для объектов, отдельно слои для фона и отдельно для тайлов. Есть ещё слои для спрайтов. Параметр depth на каждый слой устанавливается отдельно. Или вручную, или автоматически. Для объектов его можно установить теперь только в коде, а раньше это было отдельное поле.
Более того, теперь нельзя просто взять и кодом создать экземпляр объекта функцией instance_create(x,y,obj). Теперь нужно создать экземпляр на конкретном слое или с конкретным значением depth, для чего есть две отдельные функции.
При импорте IDE агрегирует всю информацию о слоях и depth и делает следующие вещи:
Огромный костыль в виде набора скриптов для того, чтобы объекты создавались на нужном слое.
В комнате создаёт кучу слов, на которые кидает расставленные по сцене объекты согласно их параметру depth.
Огромный костыль в виде набора скриптов на работу с фонами
Огромный костыль в виде набора скриптов на работу с тайлами
Примерно так это выглядит для сложных уровней:
А должно выглядеть примерно вот так:
Разумеется, для сложных уровней слоёв будет чуть больше. Кстати, при импорте сортировка слоёв и объектов ещё и сбиться может.
Итог один - нужно перебрать все сцены и все вызовы функции создания экземпляров объектов. Что займёт время. Это одна из причин, по которой я долго откладывал порт этой игры.
За неделю я сделал это для выбора языка, меню и двух интро-экранов.
Ещё один неприятный момент - это тайловая система.
В GM8 тайлы - это просто вырезанный кусочек фона. Кусочком фона он и импортируется в GMS2. И каждый такой тайл можно было положить в произвольную позицию. Тайлы могли перекрывать друг-друга. И управлять этим всем можно из кода. Что делалось максимально просто. Причём ты всегда мог легко получить координаты обрезки тайла внутри текстуры.
В GMS2 тайлы - это специальная структура, которая должна укладываться в сетку. Каждой ячейке сетки отдельный тайл и никак иначе. Тайл нельзя ставить в произвольные места, только чётко в свою клетку. И в одну клетку два тайла запихать тоже нельзя. Зато внутри этой клетки его можно зеркалить и поворачивать. А так же можно собирать пресеты из тайлов (кисти они в IDE зовутся), делать автотайлинг и анимированные тайлы. Понять место тайла в структуре просто не получится, только рассчитывать, и нужно обязательно тайловую структуру выгружать в проект как спрайт для этого, потому что по-умолчанию она идёт как тайловая текстура и работать с ней как со спрайтом не получится, а из текстуры вытащить данные проблематично.
Это всё я тут рассказал только для того, чтобы дальше сообщить, что в Zzzz-Zzzz-Zzzz есть механики, завязанные на тайлы. Например есть нечёткий сон, в котором спец-объекты брали тайл и шатали его.
А ещё есть сон телевизор, где ГГ под грибами пытается догнать солнце. Там вообще два слоя тайлов, один из которых по сюжету сгорает к чертям.
Скорее всего, по ходу дела всплывут ещё проблемы с тайлами, например окажется, что где-то тайл стоит не по сетке. Такое уже встретилось мне при выборе начать новую игру.
Касательно самих тайлов как контента есть нюансы. GM8 позволял на тайловый слой накидывать тайлы из разных ассетов. В GMS2 на тайловый слой должен быть использован специальный ассет tileset, в котором используется один спрайт. В итоге, я потратил довольно много времени при разработке второй части игры, чтобы все тайловые ассеты рассортировать на три текстуры (стены, фон, объекты.):
При импорте тайлов GMS2 создаёт не тайловый слой, а спрайтовый. Вообще он нужен, чтобы размещать на нём анимированные спрайты без создания для них объектов, как приходилось делать в GM8. Для анимированного декора, в общем. А при импорте именно в такой слой набрасывается нарезка, т.е. просто спрайт тайлсета кадрированный под конкретный тайл. Для примера, в этом кусочке сцены используется минимум 6 разных обрезанных спрайтов.
Производительности жрёт мама не горюй. И это функционал почти read-only. Можно эти огрызки подвигать, покопипастить, но новых таких создать нельзя.
Это приводит к тому, что все такие слои нужно заменить на более оптимизированные тайловые слои. Тоже займёт прилично времени.
FAQ
Отвечаю, на вероятно возникшие вопросы после прочтения.
Вопрос: Не проще было бы просто сделать проект с нуля на тех же ассетах?
Ответ: Я всерьёз об этом думал. Пришёл к выводу что игровую логику воспроизводить дольше и скучнее.
Вопрос: В расширенной версии так много сделано, неужели комнаты не адаптированы?
Ответ: Они слишком сильно адаптированы, т.е. в них появился новый контент, которого не было в оригинальной версии.
Вопрос: Почему не доделать расширенную версию?
Ответ: Это дольше гораздо. Там ещё больше сложнорешаемых проблем с камерой. У меня пока нет ресурса чтобы в это погрузиться на 3-4 месяца.
Что по итогу за неделю, и что дальше?
По итогу я протащил исходник на новую версию движка, перенастроив и заменив часть подсистем. Сейчас игра запускается без ошибок, я переделал сцены для стартовых комнат (в финал уже) - выбор языка, меню, катсцены, интро.
Дальше я буду работать над самими уровнями. За следующую неделю я сделаю Карту Снов, и наиболее простые для адаптации уровни.
Буду рад вашей поддержке:
Вступайте в мою группу ВК чтобы следить за девлогом и новостями.
Залетайте на мой дискорд Сервер - мы с пацанами там обсуждаем всякое - и мой прогресс по проектам, их концепты и просто трындим про игры, делимся опытом.
Спасибо за внимание, тем кто осилил!
Хотел по-быстрому написать средний пост, но пришлось погрузиться в технические детали и вышел длинняк.