Я люблю C.
Я любою джаву не меньше, но это совсем не то чувство первой любви, которое я испытываю к C.
Я никогда не работал с SDL, но мне кажется, что он очень удобен для написания игр. По крайней мере я бы купился на его кросс-платформенность. Тем более, что теперь в списке его платформ еще и Android.
Установка
Я использовал версию из репозитория (SDL 2.0):
$ hg clone http://hg.libsdl.org/SDL
Далее необходимо несколько изменить структуру папок, чтобы получился Android-проект.
1) Копируем папку SDL/android-project в, скажем, sdl-demo (это наша папка с SDL проектом)
2) Копируем саму папку SDL в sdl-demo/jni (получаем sdl-demo/jni/SDL)
3) Редактируем jni/src/Android.mk, заменив YourSourceHere.c на список файлов вашего проекта (для простоты — только main.c)
4) Создадим этот самый main.c (путь к нему: sdl-demo/jni/src/main.c).
5) Теперь native-часть нашего проекта должна собираться.
Вот как это было у меня:
$ cp -r SDL/android-project sdl-demo $ cp -r SDL sdl-demo/jni $ sed -i 's/YourSourceHere.c/main.c/' sdl-demo/jni/src/Android.mk $ cat > sdl-demo/jni/src/main.c << EOF int SDL_main(int argc, char *argv[]) { return 0; } EOF $ cd sdl-demo/jni $ ndk-build Compile thumb : SDL2 <= SDL_error.c Compile thumb : SDL2 <= SDL_assert.c Compile thumb : SDL2 <= SDL_log.c Compile thumb : SDL2 <= SDL.c .... Compile thumb : SDL2 <= SDL_androidkeyboard.c StaticLibrary : libstdc++.a SharedLibrary : libSDL2.so Install : libSDL2.so => libs/armeabi/libSDL2.so Compile++ thumb : main <= SDL_android_main.cpp Compile thumb : main <= main.c SharedLibrary : libmain.so Install : libmain.so => libs/armeabi/libmain.so
Доработать напильником
И хотя libSDL готова, этого еще недостаточно чтобы заставить ее работать под Android. Дело в том, что SDL для Android — это кружево из native-функций, и все они вызываются из Java-класса SDLActivity (см. sdl-demo/src/org/libsdl/app/SDLActivity.java).
Так вот, общая схема работы такая: запустить отдельный поток, а в нем SDL_main().
А после выхода из SDL_main().. не делать ничего. Хотя, логично было бы завершить Activity.
Это можно сделать руками. В просто случае это runOnUiThread()+finish(). Но я сделал через статические методы, по аналогии с COMMAND_CHANGE_TITLE:
static int COMMAND_CHANGE_TITLE = 1; static int COMMAND_QUIT = 2; // <-- НАША КОМАНДА: ЗАВЕРШИТЬ ACTIVITY ... Handler commandHandler = new Handler() { public void handleMessage(Message msg) { if (msg.arg1 == COMMAND_CHANGE_TITLE) { setTitle((String)msg.obj); } else if (msg.arg1 == COMMAND_QUIT) { // <-- ДОБАВИМ НАШУ КОМАНДУ finish(); } } }; ... public static void finishActivity() { mSingleton.sendCommand(COMMAND_QUIT, null); // <-- ОБЕРТКА ДЛЯ ВЫЗОВА COMMAND_QUIT } ... class SDLMain implements Runnable { public void run() { // Runs SDL_main() SDLActivity.nativeInit(); SDLActivity.finishActivity(); // <-- ДОБАВИМ ВЫЗОВ finishActivity //Log.v("SDL", "SDL thread terminated"); } } ...
Понимаете о чем я?
Теперь можно обновить проект, если надо, и собрать его целиком:
$ android update project <ваши опции, напр. -t 1 -p .> $ ant debug
Пример
Теперь вы можете писать приложения на SDL в main.c. Вот что получилось у меня:
#include "SDL.h" #include <android/log.h> #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "sdldemo", __VA_ARGS__) int SDL_main(int argc, char *argv[]) { SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; SDL_Texture *bitmapTex = NULL; SDL_Surface *bitmapSurface = NULL; int running = 1; LOG("started"); SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow("SDL Hello World", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); bitmapSurface = SDL_LoadBMP("data/test.bmp"); bitmapTex = SDL_CreateTextureFromSurface(renderer, bitmapSurface); SDL_FreeSurface(bitmapSurface); while (running) { SDL_Event event; SDL_RenderClear(renderer); SDL_RenderCopy(renderer, bitmapTex, NULL, NULL); SDL_RenderPresent(renderer); while (SDL_PollEvent(&event)) { if (event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_AC_BACK) { LOG("pressed back button, terminating"); running = 0; } } } LOG("finished"); SDL_DestroyTexture(bitmapTex); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }
Данный пример я по кускам спёр в разных уголках интернета, но в целом он понятен: создали окно (640х480 — это логические размеры, физические берутся из onSurfaceChanged()). Потом делаем рендерер, и загружаем текстуру. Текстура загружается из «assets/data/test.bmp».
Затем в цикле рисуем текстуру с помощью рендерера, и ждем следующего события. Особым образом обрабатываем нажание на кнопку «Back» — завершаем наш цикл. А потом чистим все ресурсы.
Итоги
Собираем, запускаем. На телефоне выглядит примерно так:
Теперь можно учить SDL и писать игры. Хотя путь еще долгий — хочется SDL_ttf, SDL_image, чтобы работать с комфортом. Но это уже совсем другая история.
Кстати, overhead от использования SDL ни много ни мало — 200-300 Кб.
Все исходники (около 6 мегабайт, SDL много весит, и так удалил тесты и папку контроля версий): https://rapidshare.com/files/4157028483/sdl-android.tar.gz
Лучше, пожалуй, какой-то скрипт для развертывания SDL-проекта написать.
UPD: Ссылка на rapidshare безнадежно устарела. Сделал заново по инструкциям для свежего SDL: https://dl.dropboxusercontent.com/u/5689099/sdl-demo.tar.gz
Спасибо за подробный разбор.
спасибо, а проект можешь целиком выложить?
Залил на rapidshare (см. ссылку внизу поста). Enjoy!
о, оперативно, спасибо!!!
ты не в курсе, как быть со старыми функциями SDL_SetVideoMode, SDL_DisplayFormat, SDL_Flip, в новой версии их нет, есть какие-то замены или как?
Посмотри в SDL/src/SDL_compat.c и SDL/include/SDL_compat.h в исходниках с сайта: http://www.libsdl.org/tmp/SDL-2.0.tar.gz
А еще может быть полезным: http://wiki.libsdl.org/moin.cgi/MigrationGuide
Бодрого!
А ты в курсе как грузится линукс(Android) в SMP multicore? Т.е. с чего он стартует (0-е ядро?) и в каком месте и как начинает использовать остальные ядра?
Ой. Чё-то не заметил сразу коммент, сорри.
Ну вкратце загрузка с SMP для интела описана тут — http://tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/smpboot.html
Вообще штука это аппаратно-зависимая, но в целом это все происходит внутри start_kernel() с вызовом smp_init().
Для армов с картинками тут немного есть — http://www.linux-arm.org/LinuxBootLoader/SMPBoot
скачал Ваш проект, ничего не менял, с помощью eclipse создал проект с готовых исходников, эмулятор андроид 4.03, запустил. в итоге на месте, где надо грузить изображение — крешится( не подскажете, в чем может быть дело?
Вынужден подтвердить. На самом деле все довольно просто:
1) Вы могли поставить эмулятор для x86 (который сборка андроида от Intel, в составе SDK, щас многие ее любят ставить, т.к. быстрее работает). Тогда нужно добавить
jni/Application.mk
с одной строчкой —APP_API := armeabi x86
2) Даже если у Вас ARM сборка, или Вы пересобрали SDL для x86 — все равно огромные шансы не заработать. Будет крэш во время
SDL_CreateRenderer()
, потому что там указано — только accelerated renderer (а эмуляторы по умолчанию, а некоторые и вообще не умеют делать аппаратное ускорение). Поэтому — либо придется тестировать на девайсе, либо шаманить с эмулятором, чтобы в нем заработал аппаратный OpenGL.3) либо я не угадал, и дело в чем-то другом 🙂
Подумал, что таки да — графическое ускорение выключено. Нашел, как включить — запускаю — та же беда. Модифицировал код (то есть как модифицировал — в ключевых местах поставил запись в лог
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
LOG(«!!!created renderer»);
bitmapSurface = SDL_LoadBMP(«data/test.bmp»);
if(bitmapSurface != NULL)
{
LOG(«!!!load image»);
bitmapTex = SDL_CreateTextureFromSurface(renderer, bitmapSurface);
SDL_FreeSurface(bitmapSurface);
}
else
{
LOG(«!!!failed load image»);
}
и судя по логах — оно создает renderrer…
05-29 09:42:48.666: D/sdldemo(592): !!!created renderer
05-29 09:42:48.676: E/dalvikvm(592): JNI ERROR (app bug): accessed stale local reference 0x1d200029 (index 10 in a table of size 3)
Я совсем не знаком с android (так случилось, что надо протестировать sdl для него), и я не понимаю, как (КАК?!?) картинка, которая находится в папке “assets/data/test.bmp”, но по отношению к коду main.c совсем не там — может подгружаться правильно? Это магия андроида?)
Странного ничего нет, во-первых путь к картинке относительный, а значит если бы current working directory была папка ./assets/, то все заработало бы как надо. Магия в том, что в андроиде ресурсы, которые лежат в assets, открываются как обычные файлы с относительным путем. По крайней мере в джаве если обращаться к файлу по пути «dir1/dir2/file.txt», от это будет соответствовать пути assets/dir1/dir2/file.txt в структуре проекта.
Наверное потому что андроид приложение — это zip-архив который на лету распаковывается и выполняется, то получается, что вся работа с файлами-ресурсами в составе приложений происходит особым образом.
Это можно увидеть в сорцах SDL: В SDL_android.c есть функиця Android_JNI_FileOpen, которая открывает файл не через fopen/open, а через java-классы типа AssetManager. Так вот SDL_rwops.c (в котором функции работы с файлами в SDL) использует как раз Android_JNI_FileOPen. Так что не удивляйтесь, все относительные пути начинаются от папки assets/.
ммм… пробовал запустить на реальном устройстве — тоже ничего не получилось. и кажется проблема как раз в загрузке изображения
LOG(«!!!created renderer»);
bitmapSurface = SDL_LoadBMP(«data/test.bmp»);
LOG(«!!!urrraa»);
до «ура» оно не доходит. ничего не понимаю…
Если интересно, то это проблема как рас 4го андроида. http://bugzilla.libsdl.org/show_bug.cgi?id=1500
Serg! Очень интересный блог и этот пост в частности! Не могу взять исходники с рапидшары — ссылка не работает… а хочется посмотреть(
ДА вот это настоящая кросплатформенность! Скопировал тупо код пример и хоть это мелочь но все работает! http://s017.radikal.ru/i427/1401/fe/894b437fc8fe.png
Спасибо, действительно нормальный пример кода.