Разработка под Android / Портация на Android

в 11:21, , рубрики: android development, Gamedev, Ndk, метки: , ,

Портация на Android

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

Движок

Движок (хобби) находится в разработке уже 10 лет.
Движок полностью написан на C/C++, до начала портации на Android поддерживал iOS и Windows.
Логика, рендеринг, звук — все на C/C++.

Файловая система

Важная особенность которая сделала портацию на Android очень простой — это файловая система движка.

Псевдо код:

 class IStream {  void setName(string name ) = 0;  open() = 0;  write() = 0;  … = 0; }  class FileStream : public IStream {  имплементация через fopen,fread... POSIX API. }  class DataPackStream : public IStream { имплементация для работы с .pak файлами Core::Meta* m_packFileStreamMeta;; /// m_packFileStreamMeta->Create() IStream* этого типа будет использован для работы непосредственно с .pak файлом } 

Итак: вся работа с файлами в движке основана на интерфейсе IStream, архитектура движка поддерживает фабрику объектов, и её кастомизацию.

Пример кода:

 Core::FileStream::Type()->setFactoryMeta(DataPack::DataPackStream::Type());  FileStream* filestream1 = FileStream::Create(); // тоже самое будет и при new FileStream() FileStream* filestream2 = FileStream::CreateExact();  filestream1 будет типа DataPackStream filestream2 будет типа FileStream 

Ничего нового и все довольно стандартно.

Так как все ресурсы запрепроцесенны в .pak файл, то для Android системы пришлось написать свою реализацию IStream специально для работы с Java.

Инициализация данного ресурса на Java

 AssetFileDescriptor RawAssetsDescriptor = this.getApplicationContext() 				.getResources().openRawResourceFd(R.raw.data000); if (RawAssetsDescriptor != null) {     FileInputStream fis = RawAssetsDescriptor.createInputStream();     NativeMethods.dataPackChannel = fis.getChannel();     NativeMethods.dataPackOffset = RawAssetsDescriptor.getStartOffset();     NativeMethods.dataPackSize = RawAssetsDescriptor.getLength(); } 

Нейтив класс все вызовы open,read,seek транслирует опять в Java

 class AndroidPackStream : public IStream { //....     // пример работы read     size_t read(void *buffer, size_t size, size_t count)     {       if( JavaHelpers::m_pClass )       {         jmethodID mid = JavaHelpers::GetEnv()->GetStaticMethodID(JavaHelpers::m_pClass, "freadDataPack", "(I)I");         int res = JavaHelpers::GetEnv()->CallStaticIntMethod(JavaHelpers::m_pClass, mid, (int)size*count); 	jfieldID field = JavaHelpers::GetEnv()->GetStaticFieldID(JavaHelpers::m_pClass,"byteBuffer","Ljava/nio/MappedByteBuffer;"); 	jobject obj = JavaHelpers::GetEnv()->GetStaticObjectField(JavaHelpers::m_pClass,field); 	uint8_t* pData=(uint8_t*)JavaHelpers::GetEnv()->GetDirectBufferAddress(obj); 	memcpy(buffer,pData,size*count); 	JavaHelpers::GetEnv()->DeleteLocalRef(obj); 	return res/size;     } } 

Java реализации чтения данного стрима с возвратом данных назад в нейтив

 public static int freadDataPack(int count) {     long curPos = dataPackChannel.position();     int countReaded = count;     byteBuffer = dataPackChannel.map(MapMode.READ_ONLY,dataPackChannel.position(), count);     byteBuffer.load();     dataPackChannel.position(curPos+countReaded);     return countReaded; } 

Важный момент — сборщик apk сжимает все ресурсы, а мы хотим читать файл как будто он просто лежит в файловой системе. Для того чтобы наш .pak файл не сжимался внутри apk — вы должны сменить ему расширение на одно из тех которые будут говорить упаковщику не сжимать данные файлы при упаковке (детали ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/). Я выбрал .imy.
Загрузка ресуров таким способом очень быстра, например на Kindle Fire работает быстрее чем на iPad 1
Оговорю стразу что такой финт можно провернуть если у вас не очень много данных.
Для большого объема данных — вы можете сами распаковать данные на внутрений носитель(например при первом запуске) и напрямую их использовать посредством функций fopen fread (в моем случае, через не подмененный FileStream) и.т.д.

Звук

Для Windows и iOS был использован OpenAL. OpenAL для Android был собран благодаря pielot.org/2010/12/14/openal-on-android/. Заработал не сразу, а только после исправлений которые описаны в комментариях на вебсайте.

Взаимодействие с нативным кодом из С/C++

За обработку вызовов платформо зависимых реализаций отвечает очень простой класс
Псевдо код:

 class IPlatfomCommandFeedback {     void onResponse(string&); }; class IPlatfomCommand {     string execute(string& command, IPlatfomCommandFeedback* feebback); }; 

Для iOS своя реализация на Objective-C а для Android своя реализация на JNI->Java.

Рендеринг

Нейтив часть работает с использованием OpenGL, для Android были сделаны минимальные изменения. Как в последствии оказалось этого было недостаточно. Дело в том что на Windows и на iOS текстуры не теряются когда приложение уходит в бакграунд, а вот для Android это происходит всегда. В движке уже был менеджер текстур (в основном для отладки) и добавить перезагрузку ресурсов оказалось не сложно.

Сборка

В самой первой статье я писал о том, что изначально компилировал iOS с использованием toolchain и у меня были настроенные makefile. Вот тут то мне это и пригодилось. Были дописаны таргеты для сборки с использованием Android NDK, добавлен степ в билдеры Eclipse и все взлетело. Да, можно использовать билд систему из Android NDK samples. Её я использовал только для выяснения параметров которые используются для вызовов gcc.

OpenFenit AdMob

Интеграция обоих библиотек прошла без проблем — четко по инструкции разработчиков

Proguard

Тут все более или менее понятно — нужно только позаботится о том чтобы ваши JNI Native бинды не обускейтились

Средства разработки

Железо: Kindle Fire, и пару планшетов и мобильных телефонов друзей(для тестирования).
Софт: Eclipse с плагинами

Шок номер 1

Android Market это не AppStore. Там все по другому. Там нет категории New.
Для сравнения на AppStore в первый день релиза было 800+ скачек, во второй 2000+, на Andoid Market официальная первая сотня скачек была получена только на третий день. Активность по раскрутке для обоих платформ была одинакова

Шок номер 2

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

Шок номер 3

Бестиарий устройств велик. Количество проблем соответственно.

Amazon

Процесс ревью на Amazon AppStore for Android занимает неделю, плюс еще несколько дней пока приложение появится в списке AppStore на Kindle Fire. Количество скачек и динамика соответствует Android Market

Реклама

Лучи ненависти Корпорации Добра — уже неделя как рекламный баннер был отправлен на апрув. До сих пор тишина.

Деньги

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

Автор: PavloG


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js