Портирование Adamant Armor Affection Adventure на Android OS

Когда-то давно, ещё до того, как была создана операционная система Android, в узких кругах гиков всего мира были распространены портативные игровые консоли, работающие под управлением GNU/Linux. Наибольшую известность из них получили девайсы от южнокорейской фирмы GamePark Holdings (GPH), которая на протяжении нескольких лет пыталась безуспешно конкурировать с Sony и её очень популярным в те времена карманным устройством PlayStation Portable в его различных вариациях. К сожалению, все крупные игровые издатели и разработчики не проявили интереса к маленькой и неизвестной компании GPH и её нескольким девайсам. А потому эти устройства так не стали популярными и не получили каких-либо эксклюзивных игр AAA-класса. Тем не менее, в этой печальной истории есть и светлый момент: портативные игровые консоли от GPH приглянулись множеству хакеров и гиков. И это не только из-за ядра Linux на борту устройства, но ещё и потому, что компания приветствовала создание игр и программ сторонними разработчиками и даже выложила в общий доступ несколько наборов SDK. Эти действия создали своеобразную атмосферу и сообщество вокруг этих девайсов и их немногочисленных обладателей. Разработчики, при спонсорской поддержке со стороны GPH и некоторых других компаний, проводили различные конкурсы, на которых писали свои собственные эксклюзивные игры и эмуляторы или делали порты уже существующих. Таким образом, изначально скудную библиотеку игр удалось значительно расширить, а в плане эмуляции консоли от GPH до сих пор могут похвастаться широкой поддержкой старых игровых платформ. Не обошла стороной устройства и демосцена, на популярном сайте-агрегаторе демок www.pouet.net для них даже было сделано несколько разделов. Несмотря на скромные продажи, у этих девайсов имеются поклонники и на просторах СНГ, они сосредоточены на нескольких русскоязычных форумах, посвящённых околоигровой тематике.



Порт игры Adamant Armor Affection Adventure, запущенный на Android-устройстве Motorola Droid 2.

В далёком 2011 году энтузиастами было организовано мероприятие RIOT Tag-Team Coding Competition, целью которого было увеличение количества Homebrew-игр на различных карманных игровых устройствах на базе ядра Linux, в том числе и на девайсах от компании GPH, которая, кстати, была основным спонсором конкурса. Одной отличительной особенностью этого мероприятия являлось то, что Homebrew-игру необходимо было разрабатывать командой, а игры от «одиночек» на конкурс не принимались. Именно поэтому двое российских разработчиков «старой школы»: Don Miguel и quasist решили объединить свои усилия и начали работать над эксклюзивным игровым проектом для актуальных на тот момент времени консолей от GPH: GP2X Wiz и GP2X Caanoo. В GPH-тусовке это были достаточно известные личности, поскольку они и раньше разрабатывали игры на родственные платформы. В 2002 году Don Miguel сделал популярную игру Super Plusha для старой и самой первой портативной консоли от GPH: GP32. Разработчик quasist был известен несколькими интересными играми под GP2X, такими как FleshChasmer: The Eve, Worship Vector и Adamant Armor Affection.

Именно Adamant Armor Affection (или сокращённо AAA) и послужил прообразом игры, представленной на конкурс RIOT Tag-Team Coding Competition. В его название было добавлено ещё одно слово и сиквел стал называться Adamant Armor Affection Adventure (или сокращённо AAAA). Круто поменялась и основная концепция: вместо 2D-платформера был представлен 3D-action в стиле Minecraft с элементами Stealth-прохождения. За три месяца ребятам удалось сделать практически невозможное: разработать достаточно производительный и отлаженный 3D-движок, создать десяток разнообразных карт и монстров, сделать несколько режимов игры, собрать всё это воедино и достойно выступить на упомянутом выше мероприятии, заняв почётное второе место. Игра AAAA максимально использовала аппаратные возможности консоли GP2X Caanoo, такие как сенсорный экран, акселерометр и виброотдача. Немного позже авторы выпустили эту игру на десктопные платформы, а разработчик, использующий ник sebt3, портировал AAAA на гибрид UMPC и игровой консоли для гиков — Pandora. В 2016 году разработчик munchluxe63 портировал эту игру на ещё одну Linux-консоль: GCW Zero.



Портативные игровые консоли от GPH: GP2X Caanoo (сверху) и GP2X Wiz, автор фотографии Anarchy.

Вдохновившись как самой игрой, так и успехом и самоотверженным трудом её авторов, я решил «воздать славу» старой GPH-тусовке и портировать эту игру на Android OS. Печально, но именно эта операционная система, точнее дешёвые китайские игровые консоли на ней (например, от фирмы JXD) и загнали последний гвоздь в крышку гроба компании GPH, которая давно уже обанкротилась и закрылась. После покупки устройства на MotoMAGX OS, я начал активно следить за событиями сообщества владельцев портативных игровых консолей. Ведь множество их наработок можно было перенести и на мой девайс, Motorola ZN5. Увы, в этом устройстве отсутствовал графический ускоритель, а потому игра AAAA, рендеринг которой был завязан на библиотеку OpenGL ES, была для меня недоступным «запретным плодом», в который очень хотелось поиграть на каком-нибудь устройстве. К счастью, сейчас в каждом Android-девайсе есть GPU, поэтому я решил осуществить свою давнюю мечту, перенеся Adamant Armor Affection Adventure на Android OS.

Содержание:

1. Краткий обзор игры Adamant Armor Affection Adventure
2. Первые шаги: подготовка игры для переноса на Android OS
3. Первая кровь: получение работоспособной версии игры на Android OS
4. Вариации сенсорного управления
5. Работа с кешем игровых данных в OBB-файле
6. Лаунчер игры, дополнительные улучшения и инструменты
7. Инструмент для распаковки текстур
8. Заключение, полезные ссылки и ресурсы

1. Краткий обзор игры Adamant Armor Affection Adventure

К сожалению, в сжатые сроки конкурса авторам не удалось тщательно проработать игру, поэтому в ней присутствуют несколько серьёзных проблем, таких как: отсутствие вменяемого сюжета, запутанные уровни, слишком активный респаун врагов и некоторые сложности с игровым балансом. Тем не менее, технически игра выполнена очень качественно, это одна из самых сильных в графическом плане Homebrew-игр на Caanoo и GP2X Wiz. Сюжет игры не сильно замысловат и излагается синтезированным женским голосом и в текстовом виде во вступительном видеоролике, который можно пропустить. После его окончания открывается доступ к главному меню, в котором можно выбрать самую первую миссию, посвящённую обучению игровой механике.



Главное меню игры, скриншот с Motorola Photon Q (превью, увеличение по клику).

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



Диск с основными миссиями игры в обучающем уровне, скриншот с Motorola Photon Q (превью, увеличение по клику).

Основные игровые уровни поражают своей разнообразностью. Главному герою придётся побывать в тёмных пещерах, вскарабкаться на гору, перебежать через населённую неведомыми существами пустошь, добраться до заброшенного замка и проникнуть в хорошо охраняемую секретную лабораторию. Игровой процесс достаточно увлекателен, кроме постоянного уничтожения врагов, приходится прыгать и по различным платформам. Очень понравилась возможность применения различной тактики прохождения. Действительно, лучше всего проходить AAAA в Stealth-режиме, аккуратно обходя и обманывая врагов. Кое-где необходимо пожертвовать своим персонажем, чтобы отвлечь монстров, преграждающих дальнейший путь.



Разнообразие основных уровней в игре, скриншоты с Motorola Photon Q (превью, увеличение по клику).

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



Использование лазерного целеуказателя и режим камеры от первого лица, скриншоты с Motorola Photon Q (превью, увеличение по клику).

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



Использование отвлекающего пения и применение игроком холодного оружия, скриншоты с Motorola Photon Q (превью, увеличение по клику).

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



Секретные мини-игры и миссии, скриншоты с Motorola Photon Q (превью, увеличение по клику).

В подобных мини-играх реализован простенький клон Creative-режима игры Minecraft, бесцельная Stealth-игра от первого лица в фэнтезийной вселенной, режим бесконечного отстрела зомби и экстремальный уровень, пройдя который можно получить специальные секретные коды.

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



Встроенный в игру редактор уровней, скриншоты с Motorola Photon Q (превью, увеличение по клику).

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

<< Перейти к содержанию

2. Первые шаги: подготовка игры для переноса на Android OS

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

Я решил исправить эту плачевную ситуацию и взялся за инструменты, которые были специально предназначены для форматирования кода и приведения его к общему стилю. Попробовав самые простые, такие как Artistic Style (astyle) и Uncrustify, я остался неудовлетворён результатом их работы. Например, astyle добавлял скобки только в последнее выражение, а не во все нужные мне. Поэтому пришлось взяться за более тяжёлую артиллерию и установить инструменты из набора clang: clang-format и clang-tidy.



Отформатированный код (слева) и изначальная ситуация, скриншот из Qt Creator (превью, увеличение по клику).

Их поочерёдное использование помогло достичь необходимого мне результата и код был отформатирован должным образом. Стоит отметить, что результат работы clang-tidy зависит от определённых дефайнов, поэтому необходимо пробежаться по всем доступным в коде определениям:

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

После успешной компиляции AAAA и проверке работоспособности игры на компьютере, я начал портировать движок с библиотек OpenGL, SDL и SDL_mixer на OpenGL ES, SDL2 и SDL2_mixer. SDL2-библиотеки уже давно доступны под Android OS и достаточно хорошо там работают. Более подробно портирование проекта с SDL на SDL2 я описал в своей предыдущей статье про Ken’s Labyrinth, поэтому не буду углубляться в эти аспекты. API библиотеки SDL2_mixer достаточно хорошо совместимо с SDL_mixer, поэтому здесь всё просто обошлось выставлением необходимой библиотеки для линковщика. А вот сделать десктопное приложение, использующее не OpenGL, а OpenGL ES было интересно.

Поскольку игра AAAA создавалась для GP2X Wiz и Caanoo, то её движок изначально использовал OpenGL ES для рендеринга, а OpenGL использовался только для десктопной версии. В своём интервью французскому форуму www.open-consoles.com, quasist рассказывал о том, что технически было сложно сделать универсальную систему рендеринга, поддерживающую как OpenGL, так и OpenGL ES. Это было крайне необходимо для продуктивной разработки игры, поскольку второй разработчик, Don Miguel, не имел подходящего железа для тестирования наработок и использовал компьютер. Так вот, все эти трудности с универсальным рендерером сегодня сошли бы на нет, поскольку на десктопных операционных системах имеются такие технологии как Mesa 3D и ANGLE, содержащие необходимые библиотеки и трансляторы. Поскольку я использую на своём ноутбуке GNU/Linux, то для этого эксперимента были выбраны библиотеки OpenGL ES, идущие в составе Mesa 3D. Код инициализации графической подсистемы получился следующий:

Библиотека SDL2 позволяет получить указатель на окно через обращение к полям структуры SDL_SysWMinfo. Этот указатель можно отправить в функцию eglCreateWindowSurface(), которая предоставляется библиотекой EGL, являющейся специальной обвязкой, реализующей интерфейс между оконной системой OS и необходимым графическим API. После вызова нужных EGL-функций, рендеринг осуществляется в необходимое мне окно. Роль SDL2-библиотеки сводится к минимальному — к созданию подходящего окна и к обработке поступающих в него событий. Примечательно, что обычная инициализация контекста OpenGL ES средствами SDL2 не работает должным образом на десктопе, хотя без проблем работает на Android OS. В сборочной системе я оставил возможность выбора между разными API для рендеринга:

Таким образом, на уровне сборки из исходного кода я могу собрать исполнительный файл игры как с OpenGL, так и с OpenGL ES. Вся описанная выше работа значительно облегчила перенос движка игры AAAA на Android OS.

<< Перейти к содержанию

3. Первая кровь: получение работоспособной версии игры на Android OS

Теперь, когда движок использовал кросс-платформенную библиотеку SDL2, его портирование на Android OS не должно было вызывать каких-либо сложностей. Подготовка проекта для сборки финального APK-приложения очень подробна расписана в файле README-android.md, который входит в комплект документации к библиотеке SDL2. Следуя изложенным там советам, я создал, настроил, собрал проект и получил готовый к установке APK-пакет. Игровые ресурсы я положил на карту памяти, в директорию /storage/sdcard1/AAAA-Data/, которую на данном этапе разработки просто захардкодил.

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

Как видно, размер массива texturepointer был задан неверно. Примечательно то, что на десктопной версии игры эта проблема никак не проявлялась. О таких опасных местах в коде должны сообщать статические анализаторы, поэтому очень полезно проверять ими свои проекты. Кстати, эту же ошибку исправил разработчик, использующий ник senquack, который участвовал в портировании этой игры на консоль GCW Zero.

Игра теперь запустилась, но на экране устройства были лишь искажения на тёмном фоне. Я долго не мог понять, в чём причина такого поведения и уже начал грешить на проблемы с OpenGL ES, но потом обнаружил, что конфигурационный файл, который читает движок игры, имеет нулевой размер. Во время экспериментов по запуску AAAA, работа движка некорректно завершилась и конфигурационный файл был испорчен. Поэтому при чтении конфига все параметры выставлялись в нулевые значения, что и было следствием видимого шума на экране. Я просто заменил конфигурационный файл на корректный и игра запустилась правильно.

Далее я столкнулся с проблемой управления главным героем игры с помощью аппаратного D-Pad’а на моём Android-устройстве. Интересным было и то, что другие кнопки на физической клавиатуре работали без проблем. Я уже встречался с такой ситуацией, когда использовал библиотеку SDL2 версии 2.0.3, хотя в 2.0.4 эта проблема была исправлена. И вот, в версии 2.0.5, она вернулась опять. Внимательно исследовав код, я обнаружил, что D-Pad в SDL2 теперь подключается как джойстик и потому управление в AAAA, заточенное под использование клавиатуры, никак не реагирует на него. Красивого решения этой проблемы я так и не нашёл, и потому решил просто пропатчить файлы SDL2 и добавить дополнительные методы:

Метод-фильтр convertJoyDpadToKeysFilter(), используя вспомогательный метод pressOrReleaseKey(), отправляет события нажатий на клавиши «W», «A», «S», «D» и «Backspace» при использовании на D-Pad’е кнопок «вверх», «влево», «вниз», «вправо» и «ОК» соответственно. Если была нажата какая-либо другая кнопка, то её обработка идёт как нажатие на кнопку джойстика.

Рабочая инициализация контекста OpenGL ES в SDL2, в отличие от таковой на десктопе, проста и выглядит следующим образом:

Изначально использовалась 16-битная глубина цветности формата RGB565, но немного поэкспериментировав, я выбрал 24-битную глубину формата RGB888.



Сравнение 16-битной (слева) и 24-битной глубины цвета, фрагмент скриншота с Motorola Photon Q (превью, увеличение по клику).

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

Так же как и авторы игры, в своём порте AAAA я задумал максимально задействовать современное железо Android-устройств, поэтому я решил достучаться до вибромотора и акселерометра. Функцию вибрации я вынес в отдельный файл android_extras.c, который компилируется только в Android-проекте:

Благодаря механизму JNI, при вызове функции doVibrateFromJNI() из нативного кода, вызывается статический метод doVibrate() из Java-кода:

Этот метод активирует вибромотор на заданный промежуток времени стандартными API в Android OS, если приложению даны соответствующие разрешения на использование вибрации. Получить значения с акселерометра оказалось ещё проще. Оказывается, в SDL2 есть внутренняя функция Android_JNI_GetAccelerometerValues(), она определена в заголовке src/core/android/SDL_android.h, относительно корня исходников SDL2. Таким образом, если в эту функцию передать массив из трёх float’ов, то в нём будут актуальные значения акселерометра по осям «x», «y» и «z»:

Узнав приблизительный диапазон трёх осей акселерометра на GP2X Caanoo, я адаптирую в соответствии с ним собственные значения с помощью переменной gsensor_scale, благодаря которой движок без труда может использовать эти значения для своих нужд. Переменную gsensor_scale я решил сделать настраиваемой, чтобы была возможность тонкой регулировки интервалов отклонений.

После получения вполне работоспособной сборки AAAA для Android OS, я решил ради интереса поэкспериментировать и попробовать полностью отказаться от библиотеки SDL2. Для этого я отнаследовался от системного класса GLSurfaceView и реализовал интерфейс GLSurfaceView.Renderer, из нативного кода убрал функцию main(), перенёс инициализацию дисплея и шаг обновления экрана в отдельные функции. В общем, я использовал ту же стратегию, что и при портировании игры Spout. Результатом этого эксперимента стала сборка AAAA, не зависящая от SDL2, она без особых проблем запускалась в эмуляторе Android-устройства:



Сборка Adamant Armor Affection Adventure, отвязанная от библиотеки SDL2 и запущенная в эмуляторе Android OS, на фоне Eclipse IDE и KDE Plasma 5 (превью, увеличение по клику).

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

<< Перейти к содержанию

4. Вариации сенсорного управления

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



Вариант простого сенсорного управления, скриншот с Motorola Photon Q (превью, увеличение по клику).

У этого метода есть как несколько плюсов, так и минусов. Самый серьёзный недостаток, это небольшая просадка FPS на слабых Android-девайсах. Но он перекрывается возможностью быстрой кастомизации управления в необходимую мне сторону без надобности внесения изменений в сам игровой движок. Подобный тип управления я уже использовал в своём порте игры Ken’s Labyrinth и не буду касаться деталей его реализации здесь.

Такой вариант мне показался не слишком удобным для Adamant Armor Affection Adventure, поэтому я решил интегрировать в игру ещё один тип сенсорного управления, который я позаимствовал у разработчика, использующего ник [SoD]Thor. Примечательно, но он тоже начинал свой путь с тусовки GPH и разработал кучу интересных игр, в том числе и трёхмерных. Спросив у [SoD]Thor’а разрешение модифицировать его код, я получил положительный ответ и взялся за подгонку его наработок под AAAA, добавив несколько новых кнопок и немного поправив джойстик:



Вариант современного и более сложного сенсорного управления с джойстиком, скриншот с Motorola Photon Q (превью, увеличение по клику).

Отличительной особенностью такого управления является наличие сенсорного джойстика, который появляется при нажатии пальцем на соответствующую часть экрана. Отклоняя палец в стороны можно управлять главным героем или перемещаться по пунктам меню. Помимо этого, вариант управления от [SoD]Thor имеет индикацию нажатия, что позволяет увидеть, была нажата кнопка или нет. Основной недостаток остался прежним: слой с сенсорными элементами накладывается поверх игрового экрана, что негативно влияет на FPS, но заметно это лишь на старых и слабых устройствах, например, на Motorola Droid 2 и аналогичных ему по техническим характеристикам аппаратах. Ещё стоит заметить, что кнопки импровизированы, а нажатия обрабатываются в пределах прямоугольных регионов, на которые разделена рабочая область дисплея.

Класс AAAAModernInputView, в котором реализован этот вариант управления, является наследником системного класса View, что позволяет использовать оверлей с сенсорными элементами как обычный виджет, имея возможность накладывать его поверх других. Расчёт направлений отклонения и определение кнопок выполняется в методах getDirection() и getButton():

Для определения кнопок, экран делится на несколько частей по горизонтали и вертикали. При перемещении пальца в эти регионы функция возвращает индекс нажатой кнопки. Отрисовка элементов управления осуществляется в стандартном методе onDraw(), который перегружен у родительского класса View:

Сенсорный джойстик просто состоит из нескольких примитивов в виде круга, которые собраны в единое целое. Сенсорные кнопки выводятся полупрозрачными изображениями, изменением параметра прозрачности у которых можно явно обозначить нажатия на них. Отлов и обработку событий сенсорного экрана можно посмотреть в исходном коде класса AAAAModernInputView, в репозитории игры на GitHub’е. Класс слишком объёмный, чтобы целиком публиковать его в этой статье.

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

<< Перейти к содержанию

5. Работа с кешем игровых данных в OBB-файле

Как уже было сказано ранее, на начальном этапе портирования Adamant Armor Affection Adventure, я разместил data-файлы на своей карте памяти, а путь к ним просто захардкодил в движке игры. Теперь настало время переработать этот момент и решить вопрос дистрибуции игрового кеша, общий размер которого составлял приблизительно 20 МБ. На первый взгляд, идеальным решением было поместить все эти файлы внутрь APK-пакета, чтобы не было никаких проблем с загрузкой дополнительных архивов и их распаковкой. Я начал экспериментировать и попробовал поместить кеш во внутреннюю директорию assets/ в APK-пакете. Благодаря использованию библиотеки SDL2 и стандартных функций для работы с файлами, определённых в заголовке SDL_rwops.h, я без особого труда смог получить доступ в эту директорию. Результатом этого эксперимента я остался недоволен: если запуск игры с кешем на карте памяти происходил чуть ли не мгновенно, то запуск с кешем из assets’ов затягивался на целую минуту. Стало понятно, что такое решение мне не подходит.

Тогда я решил посмотреть, как обходят эту проблему с медленным доступом к assets’ам другие игровые проекты. В интернете я нашёл игру Minetest, сборка которой была доступна и для Android. Оказывается, разработчики этой игры используют очень костыльный и некрасивый подход: при первом запуске Minetest они распаковывают кеш из assets’ов во внутреннее хранилище установленного приложения и получают доступ к файлам уже из него. Этот поход несколько раз подвергался критике со стороны пользователей, поскольку приложение начинает занимать вдвое больше места, а на устройстве хранения по сути имеется по две копии игровых файлов. Кроме того, ещё есть проблемы и с обновлениями: если APK-пакет и data-файлы в нём были обновлены, то игра всё равно будет использовать их старые копии во внутреннем хранилище. В обсуждении этого вопроса на GitHub, разработчики игры признают все проблемы и думают над решением этого вопроса. Тем не менее, последняя версия Minetest, на момент написания этой статьи, продолжает использовать распаковку кеша из assets’ов во внутреннее хранилище. Этот метод мной тоже был откинут, поскольку показался слишком непрактичным.

Мне ничего не оставалось, как избавится от навязчивой идеи запихнуть все игровые данные в единый APK-пакет и посмотреть в сторону специальных файлов формата OBB: APK Expansion Files. Дополнительным плюсом этого метода было то, что в Android, начиная с версии 2.3.x, была обеспечена их полная поддержка. Кеш OBB с необходимыми файлами монтируется в специальную директорию и Android-приложение получает туда доступ. Сделать такой файл можно с помощью утилиты jobb, которая входит в стандартную поставку Android SDK. Нужно просто определить директорию, задать версию, выходной файл и имя пакета:

При желании, с помощью опции -k [password] игровые файлы можно зашифровать. Но стоит отметить, что такие образы у меня никак не монтировались. Мой первый эксперимент с OBB-кешем провалился, я попробовал из нативного кода подмонтировать этот файл и у меня ничего не получалось. Тогда мой друг, a1batross, посоветовал мне использовать для этой цели Java, а в нативный движок прокидывать путь, полученный после успешного монтирования OBB-кеша. Я прислушался к его совету и начал исследовать эту возможность, но и здесь меня ждали некоторые проблемы. Дело было в том, что эти OBB-файлы, согласно документации, должны распространяться через Google Play Store, незаметно для конечного пользователя. Я не имею планов публиковать порт AAAA в магазине приложений, поэтому я решил монтировать OBB-образ вручную.

На GitHub’е я наткнулся на замечательный проект android-obb-example от разработчика wiliwe, его исходный код окончательно ответил на все мои вопросы касательно монтирования отдельных OBB-файлов. Единственная проблема, с которой я не смог разобраться и про которую я уже говорил выше, состояла в невозможности монтирования зашифрованных паролем OBB-образов. Поскольку движок AAAA работает с открытыми для модификации data-файлами, то использовать зашифрованный OBB-кеш было бы странно, поэтому я опустил этот момент и не стал разбираться в причинах ошибки. Код из проекта android-obb-example я немного модифицировал и использовал для монтирования OBB-файла и получения пути для дальнейшего доступа к файлам:

Метод checkObbMount() проверяет наличие OBB-файла и пытается его смонтировать, на эту попытку реагирует специальный экземпляр класса OnObbStateChangeListener, который в случае удачного монтирования сохраняет путь к файлам в переменную obbMountedPath и запускает игру. Движок игры сразу после запуска с помощью JNI получает значение переменной obbMountedPath из Java и уже начинает работать именно с ним:

Теперь, когда всё работало, я задумался о предоставлении пользователю возможности выбора необходимого OBB-образа из файловой системы Android-устройства. Одним из серьёзных недостатков Android OS является отсутствие стандартного файлового диалога. Каждое приложение использует собственноручно написанный велосипед. Изначально, в качестве диалога, я решил использовать возможности файловых менеджеров, предустановленных в систему с завода или установленных пользователем из магазина приложений самостоятельно. Заниматься подобными вещами в Android OS позволяет возможность запуска сторонних Activity из своего приложения. В теории это звучит достаточно интересно, но на практике обнаружилась огромная куча проблем, начиная с того, что некоторые системные менеджеры могут хитро кодировать пути до файлов и заканчивая вообще отсутствием предустановленных файловых менеджеров на некоторых устройствах.

Таким образом, я встал перед выбором: либо написать собственный костыль, либо найти проект с открытым исходным кодом, который уже решил эту проблему. Я выбрал второй путь и вспомнил про приложение от знакомой мне группы разработчиков FWGSxash3d-android-project. Этот проект посвящён переносу альтернативного движка для игры Half-Life на различные платформы, в том числе и на Android OS. В исходном коде этого приложения имеется специальный файловый диалог для выбора директории с игровыми данными, написанный, судя по комментарию в его начале, программистом, использующим ник Solexid.

Я взял все необходимые файлы из проекта xash3d-android-project, модифицировал их должным образом, чтобы вместо директории иметь возможность выбора OBB-файла и результатом моих экспериментов стал вот такой симпатичный диалог:



Файловый диалог для выбора OBB-образа, скриншоты с Motorola Droid 2, Photon Q, Droid 4 (превью, увеличение по клику).

Посмотреть его реализацию можно в классе AAAAFilePickerActivity, в репозитории моего порта игры AAAA. Иконки для него я взял из темы Adwaita проекта GNOME и немного их модифицировал. После этого я протестировал и отладил работу файлового диалога и монтирование OBB-образов на нескольких устройствах под управлением Android OS различных версий и остался вполне доволен полученным результатом.

<< Перейти к содержанию

6. Лаунчер игры, дополнительные улучшения и инструменты

Для того, чтобы пользователь мог удобно управлять самыми различными настройками игры Adamant Armor Affection Adventure, я создал специальный лаунчер, который украсил небольшой обложкой:



Лаунчер игры Adamant Armor Affection Adventure, скриншоты с Motorola Droid 2, Photon Q, Droid 4 (превью, увеличение по клику).

Я не буду подробно расписывать процесс его создания, эту информацию вы можете получить из моей статьи про портирование игры Spout. Скажу лишь одну интересную вещь: поскольку в главном меню самой игры имеется возможность изменения настроек, мне пришлось использовать два типа конфигурации: в виде конфига configuration и в виде обычных параметров. Это нужно было для того, чтобы процесс изменения настроек как из лаунчера, так и из главного меню AAAA был взаимосвязан. Класс AAAASettings, описывающий все параметры, выглядит следующим образом:

Изначально, игра хранила свои настройки в специальном конфигурационном файле donothexedit.me, но, как я упоминал в самом начале статьи, запись параметров иногда отрабатывала некорректно и он получался нулевого размера. Я решил полностью избавиться от этого файла и перейти к использованию функций внутреннего хранилища конфигурации, которые предоставляет системный класс SharedPreferences. Для этого я немного модифицировал нативный код движка АААА, добавив туда три функции:

Процедура readOtherJavaSettingsFromJNI() просто считывает значения необходимых параметров из полей класса AAAASettings в Java-оболочке и переносит их в переменные нативного движка. Функции readJavaConfigurationFromJNI() и writeJavaConfigurationFromJNI() являются аналогом процедур чтения и записи конфигурационного файла. Чтение конфигурации происходит следующим образом: из Java-оболочки с помощью JNI считывается массив configuration и разворачивается в нативный массив configdata, с которым уже работает движок AAAA. Запись конфигурации происходит примерно аналогично, с тем лишь отличием, что нативный массив configdata передаётся аргументом в статический метод writeConfiguration() в Java, который выглядит следующим образом:

Стоит отметить, что с передачей значений массива configdata в метод writeConfiguration() у меня возникли некоторые трудности. На Android OS версий 2.3.4 и 4.1.2 (там, где используется Dalvik) всё работало отлично, а на Android OS версий 6.0.1 и 7.1.1 (там, где используется ART) отправляемый массив был полностью обнулён. Решением этой проблемы стала функция SetIntArrayRegion(), определяющая необходимый регион массива конфигурации. Её вызов должен располагаться перед вызовом статического метода из Java-класса и отправкой в него массива.



Внутриигровые настройки Adamant Armor Affection Adventure, скриншот с Motorola Photon Q (превью, увеличение по клику).

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



Раздел настроек Other & Cheats, фрагмент скриншота с Motorola Photon Q.

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

Для сборки десктопной версии Adamant Armor Affection Adventure я решил использовать сборочную систему CMake и написал специальный сценарий CMakeLists.txt:

Путём подстановки нужного значения в переменную GLES, можно собрать исполнительный файл с поддержкой OpenGL или OpenGL ES. Кроме того, скрипт разворачивает необходимые data-файлы в директорию сборки, поэтому после компиляции игру можно сразу запустить. Стоит отметить, что команда разработчиков CMake отказалась от политики предоставления модулей нахождения новых библиотек, поэтому я вручную разместил их в директории jni/src/cmake/, относительно корня проекта:

Таким образом, сборка и запуск игры под GNU/Linux сводится к выполнению следующих команд:

CMake предоставляет возможность работы с исходным кодом движка игры AAAA из различных IDE, таких как Qt Creator и CLion:



Десктопная сборка Adamant Armor Affection Adventure под GNU/Linux с использованием сборочной системы CMake, на фоне Qt Creator IDE и KDE Plasma 5 (превью, увеличение по клику).

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

<< Перейти к содержанию

7. Инструмент для распаковки текстур

Для создания стилизованной иконки приложения мне понадобилось распаковать игровые текстуры, находящиеся в файле textures.gfx, с которым работает движок игры. Формат этого файла был мне неизвестен, его поверхностный анализ в HEX-редакторе мне ничего особого не дал. Тогда я залез в исходный код Adamant Armor Affection Adventure и обнаружил, что текстуры в этом файле хранились как raw-данные, в формате GL_RGBA/GL_UNSIGNED_SHORT_4_4_4_4 и были слеплены в одну кучу.

Используя куски кода из движка AAAA, я решил написать простенький распаковщик текстур, который сохранял бы их в обычных изображениях PNG-формата. Для этого текстуру из RGBA4444 нужно было предварительно конвертировать в формат RGBA8888, после чего для сохранения PNG-изображения можно было использовать функцию lodepng_encode32_file() из маленькой библиотеки LodePNG. Для конвертации я воспользовался небольшой таблицей аппроксимации, которую нашёл на сайте GameDev.ru, являющимся самым популярным русскоязычным форумом для разработчиков игр. В итоге, моя реализация распаковщика на языке программирования C++, заняла меньше ста строк:

В функции convertsavetexture() имеется специальный цикл, который занимается раскладыванием каждого пикселя в RGBA4444 на составляющие и подбирает к ним необходимые значения из множества RGBA8888, представленных в таблице lookupTable4to8. Для компиляции, распаковки текстур и создания коллажа из них, нужно использовать следующие команды:

Распакованные текстуры сохраняются в директории tools/gfx_unpacker/unpacked/, коллаж из текстур tex_collage.png, сделан с помощью утилиты montage, которая входит в консольный пакет программ для работы с изображениями ImageMagick.



Коллаж из распакованных текстур, скриншот из просмотрщика изображений Nomacs.

Таким образом, я выполнил поставленную задачу и выдернул всю графику из файла textures.gfx, которая позволила мне создать иконку для приложения. Цели написать упаковщик текстур я перед собой не ставил, но не вижу особых сложностей в его реализации. Если у меня появится чуточку больше свободного времени, то я займусь и упаковщиком.

<< Перейти к содержанию

8. Заключение, полезные ссылки и ресурсы

Портирование игры Adamant Armor Affection Adventure на Android OS дало мне огромное количество ценного опыта. В первую очередь стоит отметить то, что я научился работать с образами кешей в формате OBB. Кроме того, я убедился в том, что порт библиотеки SDL2_mixer на Android OS получился достаточно работоспособным. Я смог написать некоторые вспомогательные утилиты, решить множество интересных проблем и интегрировать в проект современный вариант сенсорного управления и файловый диалог.



Порт игры Adamant Armor Affection Adventure, запущенный на Android-устройствах Motorola Photon Q и Droid 2, демонстрация на видеохостинге YouTube.

Размер установочного APK-пакета получился весьма небольшим, всего лишь 1.7 МБ для трёх официально поддерживаемых библиотекой SDL2 архитектур: armeabi, armeabi-v7a и x86. Размер игрового кеша в формате OBB, как уже и было сказано выше, составляет 19.8 МБ.



Игра Adamant Armor Affection Adventure, уровень Last Laboratory, скриншоты с Motorola Photon Q (превью, увеличение по клику).

Скачать APK-пакет Adamant Armor Affection Adventure, готовый для запуска на любом Android-устройстве, и кеш игры можно по этим ссылкам:

[Скачать | Download] — APK-пакет Adamant Armor Affection Adventure, v1.0, armeabi, armeabi-v7a, x86, 1.7 МБ.
[Скачать | Download] — OBB-кеш Adamant Armor Affection Adventure, v1.0a, 19.8 МБ.
[Скачать | Download] — OBB-кеш Adamant Armor Affection Adventure, v1.0b, 22.0 МБ.

Дополнительно установочные файлы можно получить со следующих зеркал: Yandex.Disk и GitHub Releases.

Все исходные коды и проект в целом выложен в репозиторий на ресурсе GitHub. Мои изменения и исходные файлы доступны под лицензией MIT. Ссылка на репозиторий:

https://github.com/EXL/AdamantArmorAffectionAdventure

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

  1. Ветка околоигрового форума GBX.Ru, посвящённая консолям от фирмы GPH;
  2. Анонс проекта Adamant Armor Affection Adventure на русскоязычном форуме GP2X Community;
  3. Обзор игры Adamant Armor Affection Adventure от Hahahoj;
  4. Интервью главного разработчика игры AAAA, quasist’а французскому форуму, перевод на русский язык от Hahahoj;
  5. Релиз порта AAAA на Pandora от sebt3 на официальном форуме Pandora и Pyra;
  6. Исходный код порта AAAA на GCW Zero от munchluxe63, расположен на ресурсе GitHub;
  7. Официальный сайт мероприятия RIOT Tag-Team Coding Competition.

Update 29-APR-2017: Пользователь форума GBX.ru, использующий ник Lock_Dock122, попросил меня сделать сборку AAAA для игрового смартфона Xperia™ PLAY и дал мне ссылку на специальный документ, в котором описывается назначение и код каждой физической клавиши на этом аппарате. Я немного подредактировал фильтр обработки кнопок и собрал APK-пакет под Xperia™ PLAY, который Lock_Dock122 успешно протестировал.



Порт игры Adamant Armor Affection Adventure, запущенный на Android-устройстве Sony Ericsson Xperia™ PLAY (превью, увеличение по клику).

Игра была выложена на форум 4PDA, в специальную тему, посвящённую играм для Xperia™ PLAY. Чтобы не делать подобные сборки для аппаратов с различными и своеобразными элементами управления, мне было необходимо ранее заложить в приложение возможность задания аппаратных клавиш для различных действий игрока. Если у меня появится свободное время, я обязательно обновлю игру и реализую эту функциональность. Скачать версию AAAA для смартфона Xperia™ PLAY можно со страницы проектов.

Update 05-MAY-2017: Проект был переведён с устаревших технологий ant и Eclipse ADT на Gradle и Android Studio. Это ознаменовало выход новой версии Adamant Armor Affection Adventure v1.1, скачать которую можно со странички проектов.

<< Перейти к содержанию

Android, Dev

Портирование Adamant Armor Affection Adventure на Android OS: 29 комментариев

  1. О таких опасных местах в коде должны сообщать статические анализаторы

    тут мог бы вытянуть проблему и Valgrind: обращение за пределы массива. Это из доступного.

    1. Ага, второй человек, который исправил эту же проблему в порте этой игры на GCW Zero, как раз использовал Valgrind.

    1. На Photon Q я использую стоковый Android 4.1.2, но для него доступен вполне стабильно работающий LineageOS 13 (Android 6.0.1).

      1. Android начиная с версии 5.0 стал каким-то жутко тормозным. А 4.1.2 уже конечно староват. У тебя интересный блог и контент, не думал попробовать портировать xray на android, исходники вроде есть?

        1. На втором QWERTY-устройстве (Droid 4) у меня как раз Android 6.0.1, работает вполне сравнимо со стоком по производительности.

          У тебя интересный блог и контент, не думал попробовать портировать xray на android, исходники вроде есть?

          Спасибо. Я слышал про X-Ray, более того, я слышал про OpenGL-рендер в X-Ray: https://github.com/Armada651/xray/, в теории можно прикрутить и OpenGL ES-рендер, либо попробовать использовать разные прослойки. Но чую, с производительностью будут серьёзные траблы.

  2. Неплохой порт. На моем expkay N1 идет без тормозов Только есть моменты. Haptics пришлось на минимум зафигачить, чтобы управление не было слишком резким. И почему-то у фонаря нет плавного освещения текстур, хотя свет от ракетницы подобной вещью обладает. Дня через два выложу к себе. Пусть народ приобщается.

    1. Спасибо за тест. У Explay N1 вроде Mali-400 MP, возможно поэтому с фонариком проблемы. Я тестировал на Adreno 225 и PowerVR SGX530 и SGX540, и такой проблемы не замечал. Буду рад, если скриншот сделаешь, чтобы хоть сориентироваться немного.

      1. Блин, frameskip жёстко валит. Ставишь 0 — слишком медленно. Ставишь 2 — слишком быстро. При 1 более-менее, но на таком экране сильно не разгуляешься. Очень понравилось управление(по сравнению с ken’s labyrinth)
        Скрины выложу чуть позже

    1. Благодарю. Удивлён, что на таком низком разрешении, как 480×320, управление выглядит более-менее нормально. Изначально порт делался для 854×480. Насчёт фонарика, он таким и должен быть по задумке авторов игры. То есть просто освещать текстуры вблизи игрока в виде «свечки», а не направленным лучом.

  3. Меня интересует не освещение, а отсутствие плавности засвета текстур фонарика. Просто ты находишься в секторе, только сделал шаг вперед, и текстура резко освещается. Хотя при выключении фонарика идет плавное затухание. Весь свет фонарика дерганно выглядит, как будто блоки новые из темноты Стив из майнкрафта тебе ставит. Даже свет от ракетницы не страдает такой вещью

    1. Просто ты находишься в секторе, только сделал шаг вперед, и текстура резко освещается.

      Ага, понял о какой ты проблеме. Она хорошо видна на вот этом моменте видеоролика.

      Я посмотрел видео, и понял, что это не изменить:(

      Будет время, попробую поколдовать. К счастью, тёмных мест в игре не так много.

        1. А что вид от первого лица делает?

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

          И ещё, рассылка новых тем у тебя автоматическая?

          Оповещений об ответе на комментарий? Да. Отключить? А то поди спамит.

            1. А, ты про письмо. Нет, это я просто сам тебе написал. А вообще если у тебя есть RSS-читалка, то о новых постах можно узнавать через ленту Atom. В современных браузерах куча расширений для чтения RSS и Atom лент.

  4. А rss у тебя весь текст поста содержит?
    А то, несмотря на прекрасную вёрстку сайта и отсутствие тормозов, сайт (((ооооо)²)³)⁴чень долго грузится на эксплее. А килобайтная интепритация многотонной странички позволила бы читать ее хоть на ходу, а откомментировать тебе уже на компе.

    1. А rss у тебя весь текст поста содержит?

      Да, конечно. Можешь поставить программу-читалку RSS (советую RSSDemon) и добавить сайт в читаемые, нажав на кнопку RSS сайта в браузере (она на первом скрине слева в самом низу).

      Потом просто жмёшь Update, ждёшь когда синхронизируется и можешь читать. Но подсветка того же кода будет убита. Помни об этом.

  5. Спасибо. Не ради кода тебя читаю. Не дорос еще. Поздно спохватился, а на носу ЕГЭ. Там выучу паскаль, чтобы нормально написать, позже подумываю учить си шарп наверное

  6. Как всегда, отличная работа! Единственное джойстиком играть неудобно, надо подключать внешнюю клаву.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *