В этом посте я расскажу о том, как перенёс свою Flash-игру Frog Fractions на современную платформу. В результате я создал частично автоматизированный порт на Unity при помощи Haxe. Пост будет интересен всем, кто пытается модернизировать свою кодовую базу на Flash. В посте будут приведены спойлеры о структуре Frog Fractions: Game of the Decade Edition и её DLC Hop's Iconic Cap.
После того, как игре Frog Fractions 2 не удалось сделать меня богатым, я примерно год занимался работой по контракту и скетчами/прототипами. Потом моя жена забеременела, и я решил, что настало время найти настоящую работу, чтобы можно было содержать семью. Перед началом GDC 2018 я опубликовал твит о поиске работы, надеясь найти на конференции нужных людей. Я прошёл собеседования в нескольких местах, но самым важным стало то, что мне удалось найти финансирование для своего следующего проекта.
Проект был таким: спрятать следующую игру Frog Fractions внутри Frog Fractions 1 и продать её в Steam.
Frog Fractions состоит примерно из 13k строк кода на Actionscript 3 и собрана с помощью компилятора Flex. Я прикинул, что после добавления нового контента ремастера объём утроится, примерно до 40k строк кода. (И моя оценка оказалась довольно точной — получилось 28k кода на C# и 3k строк скриптов катсцен/деревьев диалогов в дополнение к оригинальной игре.)
Я рассмотрел несколько возможных вариантов дальнейшей работы:
- Продолжить писать игру на AS3 и собрать её с помощью Adobe AIR. Это был самый простой способ, но он имел несколько важных недостатков:
- Frog Fractions имела программный рендеринг и создавалась под разрешение 640x480. При более высоком разрешении картинка становилась очень некрасивой. Такое ограничение было вполне приемлемым для браузерной игры в 2012 году но, скорее всего, в 2020 году в Steam никого не устроит.
- Flex уже начал выдавать ошибки памяти, когда кодовая база оригинала игры превысила 10k строк кода, если я не делал полной пересборки. Сломается ли всё окончательно при 40k строк? (Специалисты говорят, что нет, и что мне достаточно настроить флаги компилятора.)
- Flash уже находился при смерти. Будет ли вообще существовать AIR в 2020 году? (Оказалось, что он жив и хорошо себя чувствует! Прекратилась поддержка только браузерного плагина.)
- Я подумывал о том, чтобы когда-нибудь перенести игру на консоли. Люди выпускали целые игры, полностью созданные в Scaleform (The Banner Saga, Road Not Taken), но было непонятно, будет ли жив Scaleform или что-то подобное в 2020 году. (Оказалось, что в 2018 году Autodesk прекратила продажи Scaleform, а все обсуждения о его замене касались только самой притягательного его аспекта — дизайна UI.)
- Встроить в мою игру Flash VM и перехватывать её код ввода-вывода. Существуют виртуальные машины Flash с открытым исходным кодом, но ни одна из них не показалась мне доступной или достаточно готовой для моего проекта. Сегодня я слышу хорошие отзывы о Ruffle. Возможно, она неплоха?
- Портировать игру на C#/Unity. Потребуется куча ручного труда и довольно скучная работа по запуску игры.
- Собрать два исполняемых файла: загружать игру на Flash, а когда игрок доберётся до нового контента, переключаться на Unity. Я представлял, что «как-нибудь» заставлю два исполняемых файла выполнять отрисовку в одном окне. Уверен, что это реализуемо, но, вероятно не из Unity, и подобные вещи обычно очень хрупки и обязательно ломаются при следующем обновлении Windows. (Если бы сегодня к моей голове приставили пистолет и заставили это сделать, то я, вероятно, открыл бы TCP-соединение между двумя процессами и передавал бы по нему видео. У Flash имелась хорошая поддержка воспроизведения видео, и я уверен, что существует плагин Unity, выполняющий кодирование видео.)
- Портировать игру на Haxe и OpenFL. Есть инструмент под названием as3hx для преобразования кода на AS3 в очень похожий код на Haxe, а OpenFL пытается стать спасательной капсулой для Flash API. Я с неделю поигрался с OpenFL и пришёл к выводу, что на то время он был недостаточно завершённым.
Хочу здесь подчеркнуть, что все эти подходы основывались на том, что Frog Fractions по своей сути была программой,, которую я писал для компиляции в SWF. Не могу судить, насколько моё решение применимо к вашей игре, если она является таймлайном анимации Flash, интерактивность которой обеспечивают скрипты.
Я поспрашивал советов о том, как мне двигаться дальше, и самый полезный мне дал Ларс Дусет, кроме прочего сообщивший, что у Haxe есть C#-бэкенд и его целевой платформой может быть Unity. Ещё он посоветовал мне сначала придумать рабочий процесс и портировать с его помощью небольшой проект.
(Примечание для себя: добавить Ларса Дусета в титры игры. Я не мог представить, насколько сильное влияние он оказал на прокт, пока не стал перечитывать нашу переписку.)
Спустя неделю экспериментов мне удалось запустить Frog Infarctions (игру, которую я написал 0-hour game jam Соса Сосовски) в редакторе Unity. Результат был неидеальным, но его оказалось достаточно, чтобы понять возможность реализации, поэтому я начал работу над основным проектом.
Вот каким получился мой процесс работы. Эти операции нужно выполнить один раз:
- С помощью as3hx преобразовать код с AS3 на Haxe.
- При необходимости вручную подчистить созданный as3hx код.
После этого итеративно выполнять следующие операции:
- Портировать кодовую базу на Haxe, чтобы использовать Unity API вместо Flash API.
- Использовать C#-таргет языка Haxe для компиляции кода Haxe на C#. Если сообщить Haxe, где находится UnityEngine.dll, он вытащит API оттуда и их можно будет вызывать из кода hx. То же самое можно проделать при помощи Assembly-Csharp.dll с кодом на C#, написанным для проекта.
- Выполнить сборку и протестировать в Unity.
Первая неделя работы была очень утомительной, приходилось подчищать выражения, которые as3hx помечал как противоречивые, устранять добавляемые им ошибки компилятора и разбираться с вызовами ввода-вывода. Устранение ошибок в Haxe неидеально, поэтому при каждой попытке сборки возникало около 30 ошибок, как бы я ни старался, поэтому я понятия не имел, насколько близок к завершению, пока всё не компилировалось без ошибок. Кроме того, мне кажется, as3hx — это транслятор типа «поиск и замена», а не настоящий транспайлер с переводом в AST и обратно, потому что он, похоже, добавляет такие ошибки, как расположение кода с неправильной стороны от скобок, когда в моём управлении потоками используется специфический стиль скобок.
Завершив с этим, я получил кодовую базу на Haxe, успешно компилирующуюся в C#-таргет. После этого я ни разу не касался кода AS3. (За исключением описанного ниже процесса экспорта графических ресурсов.) На этом этапе я решил отказаться от Haxe и просто работать на C#, но хотя выходные результаты as3hx довольно похожи на первоначальный код с сохранением стиля скобок, порядка кода и комментариев,, выводимый Haxe код на C# очевидно не предназначен для человеческого глаза. Поэтому с этого момента, если я не создавал новые сцены, я редактировал код на Haxe и компилировал его в C#.
На этом этапе код компилировался и запускался в движке Unity, но без ввода-вывода. Следующим шагом стало воссоздание всего кода ввода-вывода с помощью Unity API. На это и на преобразование ресурсов ушло около четырёх месяцев, в основном потому, что в Frog Fractions есть множество разных типов ресурсов:
- Векторная графика, нарисованная в редакторе Flash. Я рендерил её в качестве 4k, заставив AIR собрать исходную игру, но не делая ничего, кроме рендеринга кадров анимации и записи их в файлы PNG. Затем с помощью TexturePacker я собрал их в спрайтшиты.
- Векторная графика, использующая Flash Shape API. Во Flash всего в нескольких строках кода можно, например, отрисовать скруглённый прямоугольник и применить фильтры в стиле Photoshop типа скоса или тени. Язык лягушки отрисовывался как толстая линия с закруглёнными концами. В Unity подобные вещи реализовать сложнее. В разных случаях я применял разные приёмы, например, для каждого стиля контейнера текста рендерил 9-slices, а язык отрисовывал заполненным четырёхугольником с отмасштабированным кругом в качестве концов.
- Графика в SVG, взятая с OpenClipArt.org. Так как я записывал имена художников, чтобы указать их в титрах, мне не сложно было найти их на сайте и скачать графику в более высоком разрешении.
- То же самое касалось фотографий, взятых с Flickr. Я использовал для увеличения размера preserve details из Photoshop, чтобы при необходимости вносить изменения, и создал конвейер для разбивки полноэкранного 4k-изображения на текстуры в удобном размере со сторонами, равными степеням двойки.
- Нарисованная от руки растровая графика. В некоторых случаях художники изначально представили мне графику высокого разрешения, которую я уменьшил для оригинальной игры. В других я заплатил художникам, чтобы они перерисовали их собственную графику в 4k. Сложнее всего было убедить их, что мне не нужна более качественная графика, которую они научились рисовать за прошедшие восемь лет — я хотел как можно ближе придерживаться оригинальной игры!
- Пещерный лабиринт сам по себе был огромной проблемой. В оригинальной Flash-игре он был огромными изображениями, которые игра разрезала на куски экранного размера и пропускала через алгоритм наподобие Marching-Cubes для построения меша коллизий. Так как я не хотел увеличивать эти гигантские изображения до разрешения 4k, я применил тот же алгоритм, но с более высоким разрешением для генерации отображаемого меша. Оба меша включены в получившуюся игру, а изображения я убрал.
- Множество полноэкранных эффектов, которые нужно было реализовать заново в виде шейдеров.
- Короткий видеоклип с развевающимся флагом. Когда в 2015 году я создавал Unboxing Story Unity API для воспроизведения видео был ужасным. К 2018 году он перестал быть ужасным. Ура!
- Со звуком всё было проще. У меня сохранились все звуковые эффекты в файлах .wav и я сразу смог их использовать.
- Оригинальная музыка была записана в MP3 с форматом 64kbps mono, что, наверно, сэкономило мне за все эти годы несколько тысяч долларов затрат на хостинг Amazon S3, но у меня уже были высококачественные стереозаписи всей музыки с тех пор, когда я создавал OST.
Всё в ремастере, кроме полноэкранных шейдеров и воспроизведения видео, и большинство того, что есть в новом сюжете, рендерится при помощи вызовов Graphics.DrawMesh, а не как объекты в графе сцен Unity. Поэтому возникает вопрос: действительно ли Unity подходит для этого проекта?
В то время, когда мы с Крейгом Тимпани выбирали движок для Glittermitten Grove, мы остановились на Unity из-за её хорошей поддержки кроссплатформенности, потому что FNA ещё не был готов и потому что создание собственного движка — отличный способ никогда не закончить игру. В конечном итоге, хоть меня и печалила плохая поддержка 2D в Unity, это позволило нам почти без проблем выпустить версии для Mac и Linux, что, по моему мнению, огромная удача. (Предположительно, поддержка 2D в Unity с 2015 года стала лучше, но я её не изучал. Работа Graphics.DrawMesh вполне меня устраивает. Может, даже у FNA теперь появился работающий конвейер обработки ресурсов!)
Для этого проекта я выбрал Unity в основном по инерции — не хотелось заморачиваться изучением нового движка, ведь мне предстояло реализовать описанный выше исследовательский проект; Unity поддерживает все платформы, на которые я хотел портировать игру, в том числе и консоли.
А, ну и потом я создал дополнительную игру! Вероятно, я расскажу об этом творческом процессе в другом посте, здесь упомяну только то, что я собрал в Haxe переходную сцену, а остальную часть новой игры писал на C#. Кроме того, я внёс значительные изменения в основное меню внутри кодовой базы Haxe. Поэтому мне кажется, что вполне возможно после портирования на Haxe продолжать выпускать обновления кода, выполняя разработку на Haxe.
Также я внёс некоторые оптимизации в код оригинала. Код на AS3 намеренно писался без учёта эффективности, потому что я пытался избежать стремления к «безупречному коду». Например, я поверить не мог, что мне сойдёт с рук выделение Vector2 для кучи при каждой арифметической операции! Получившийся результат хорошо работал на компьютерах 2012 года, но части с наихудшей производительностью после двойной транспиляции вызывали значительную нагрузку, поэтому для хорошей работы на современных PC мне пришлось переписать часть кода на нативном C#.
Также стоит заметить, что если бы я не планировал значительно расширить объём оригинальной игры и просто пытался сохранить от смерти вместе с Flash, то OpenFL, AIR или Ruffle были бы, наверно, более умным выбором.
Выпуск игры походил на прощание с ней. Мне нравилось работать на Flash. В то время это был лучший способ показать свои игры людям с наименьшими усилиями и казалось, что они будут жить вечно — SWF из 90-х по-прежнему безупречно работают в последнем Flash player, хотя прошли десятки лет. Наблюдать за тем, как мир пытается перейти на HTML5, когда он явно ещё к этому не готов, было мучительно. (И, честно говоря, всё становится скорее хуже, чем лучше. Я считаю, что браузер никогда больше не станет пригодной для игр платформой, пока владельцы двух самых популярных браузеров имеют собственные магазины приложений для телефонов.) Я создал ремастер частично из-за того, что хотел продать новую игру, но ещё и из искреннего желания сохранить свою часть игровой истории. Спасибо, что вернулись к ней вместе со мной!
anonymous
Ребята из IceStone справились бы с портированием этой игры на чистый html5 за пару дней.
shaman4d
ага и забрали бы себе 50% прибыли…