Портация на 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