Начните работу с GameActivity, частью Android Game Development Kit .
В этом руководстве описано, как настроить и интегрировать GameActivity , а также обрабатывать события в вашей игре для Android.
GameActivity помогает перенести вашу игру на C или C++ на Android, упрощая процесс использования важных API. Ранее рекомендуемым классом для игр был NativeActivity . GameActivity заменяет его в качестве рекомендуемого класса для игр и обратно совместим с API уровня 19.
Пример использования GameActivity можно найти в репозитории games-samples .
Прежде чем начать
Чтобы получить дистрибутив, ознакомьтесь с релизами GameActivity .
Настройте сборку.
На Android Activity служит точкой входа в игру, а также предоставляет Window для отрисовки. Многие игры расширяют это Activity собственными классами Java или Kotlin, чтобы обойти ограничения NativeActivity и использовать код JNI для связи с кодом игры на C или C++.
GameActivity предоставляет следующие возможности:
Наследует от
AppCompatActivity, что позволяет использовать компоненты архитектуры Android Jetpack .Отображается в виде
SurfaceView, который позволяет взаимодействовать с любым другим элементом пользовательского интерфейса Android.Обрабатывает события активности Java. Это позволяет интегрировать любой элемент пользовательского интерфейса Android (например,
EditText,WebViewилиAd) в вашу игру через интерфейс C.Предлагает C API, аналогичный
NativeActivity, и библиотекуandroid_native_app_glue.
GameActivity распространяется в виде Android-архива (AAR) . Этот AAR содержит Java-класс, который вы используете в своем AndroidManifest.xml , а также исходный код на C и C++, который связывает Java-часть GameActivity с реализацией приложения на C/C++. Если вы используете GameActivity версии 1.2.2 или более поздней, также предоставляется статическая библиотека C/C++. В соответствующих случаях мы рекомендуем использовать статическую библиотеку вместо исходного кода.
Включите эти исходные файлы или статическую библиотеку в процесс сборки с помощью Prefab , что позволит вашему проекту CMake или сборке NDK получить доступ к нативным библиотекам и исходному коду.
Следуйте инструкциям на странице Jetpack Android Games , чтобы добавить зависимость библиотеки
GameActivityв файлbuild.gradleвашей игры.Для включения префаба в Android Plugin Version (AGP) 4.1+ выполните следующие действия:
- Добавьте следующее в блок `
androidфайла `build.gradleвашего модуля:
buildFeatures { prefab true }- Выберите версию префаба и укажите её в файле
gradle.properties:
android.prefabVersion=2.0.0Если вы используете более ранние версии AGP, следуйте инструкциям по настройке, указанным в документации к префабу .
- Добавьте следующее в блок `
Импортируйте в свой проект либо статическую библиотеку C/C++, либо исходный код C/++ следующим образом.
Статическая библиотека
В файле
CMakeLists.txtвашего проекта импортируйте статическую библиотекуgame-activityв модуль префабаgame-activity_static:find_package(game-activity REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PUBLIC log android game-activity::game-activity_static)Исходный код
В файле
CMakeLists.txtвашего проекта импортируйте пакетgame-activityи добавьте его в целевую задачу. Пакетgame-activityтребует наличияlibandroid.so, поэтому, если он отсутствует, вам также необходимо его импортировать.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)Кроме того, добавьте в
CmakeLists.txtвашего проекта следующие файлы:GameActivity.cpp,GameTextInput.cppиandroid_native_app_glue.c.
Как Android запускает вашу Activity
Система Android выполняет код в вашем экземпляре Activity, вызывая методы обратного вызова, соответствующие определенным этапам жизненного цикла Activity. Для того чтобы Android запустил вашу Activity и игру, вам необходимо объявить Activity с соответствующими атрибутами в Android Manifest. Для получения дополнительной информации см. раздел «Введение в Activity» .
Манифест Android
В каждом проекте приложения должен быть файл AndroidManifest.xml в корневом каталоге исходного кода проекта. Файл манифеста содержит важную информацию о вашем приложении для инструментов сборки Android, операционной системы Android и Google Play. Это включает в себя:
Название пакета и идентификатор приложения для уникальной идентификации вашей игры в Google Play.
Компоненты приложения, такие как действия, сервисы, приемники широковещательной рассылки и поставщики контента.
Разрешения на доступ к защищенным частям системы или другим приложениям.
Совместимость устройств позволяет указать аппаратные и программные требования для вашей игры.
Имя нативной библиотеки для
GameActivityиNativeActivity( по умолчанию libmain.so ).
Внедрите GameActivity в свою игру.
Создайте или укажите основной Java-класс для вашей активности (тот, который указан в элементе
activityв файлеAndroidManifest.xml). Измените этот класс, чтобы он наследовал классGameActivityиз пакетаcom.google.androidgamesdk:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }Убедитесь, что ваша нативная библиотека загружается в начале с помощью статического блока:
public class EndlessTunnelActivity extends GameActivity { static { // Load the native library. // The name "android-game" depends on your CMake configuration, must be // consistent here and inside AndroidManifect.xml System.loadLibrary("android-game"); } ... }Добавьте свою нативную библиотеку в файл
AndroidManifest.xmlесли имя вашей библиотеки отличается от имени по умолчанию (libmain.so):<meta-data android:name="android.app.lib_name" android:value="android-game" />
Реализуйте функцию android_main.
Библиотека
android_native_app_glue— это библиотека исходного кода, которую ваша игра использует для управления событиями жизненного циклаGameActivityв отдельном потоке, чтобы предотвратить блокировку в основном потоке. При использовании библиотеки вы регистрируете обратный вызов для обработки событий жизненного цикла, таких как события сенсорного ввода. АрхивGameActivityвключает собственную версию библиотекиandroid_native_app_glue, поэтому вы не можете использовать версию, включенную в релизы NDK. Если ваши игры используют библиотекуandroid_native_app_glue, включенную в NDK, переключитесь на версиюGameActivity.После добавления исходного кода библиотеки
android_native_app_glueв ваш проект, она будет взаимодействовать сGameActivity. Реализуйте функциюandroid_main, которая вызывается библиотекой и используется в качестве точки входа для вашей игры. Ей передается структураandroid_app. Она может отличаться для вашей игры и движка. Вот пример:#include <game-activity/native_app_glue/android_native_app_glue.h> extern "C" { void android_main(struct android_app* state); }; void android_main(struct android_app* app) { NativeEngine *engine = new NativeEngine(app); engine->GameLoop(); delete engine; }Обрабатывайте
android_appв основном игровом цикле, например, опрашивайте и обрабатывайте события цикла приложения, определенные в NativeAppGlueAppCmd . Например, следующий фрагмент кода регистрирует функцию_hand_cmd_proxyв качестве обработчикаNativeAppGlueAppCmd, затем опрашивает события цикла приложения и отправляет их зарегистрированному обработчику (вandroid_app::onAppCmd) для обработки:void NativeEngine::GameLoop() { mApp->userData = this; mApp->onAppCmd = _handle_cmd_proxy; // register your command handler. mApp->textInputState = 0; while (1) { int events; struct android_poll_source* source; // If not animating, block until we get an event; // If animating, don't block. while ((ALooper_pollOnce(IsAnimating() ? 0 : -1, NULL, &events, (void **) &source)) >= 0) { if (source != NULL) { // process events, native_app_glue internally sends the outstanding // application lifecycle events to mApp->onAppCmd. source->process(source->app, source); } if (mApp->destroyRequested) { return; } } if (IsAnimating()) { DoFrame(); } } }Для более подробного ознакомления изучите реализацию примера Endless Tunnel NDK. Основное отличие будет заключаться в способе обработки событий, как показано в следующем разделе.
Обработка событий
Чтобы разрешить обработку событий ввода вашим приложением, создайте и зарегистрируйте фильтры событий с помощью android_app_set_motion_event_filter и android_app_set_key_event_filter . По умолчанию библиотека native_app_glue разрешает только события движения от источника SOURCE_TOUCHSCREEN . Для получения подробной информации ознакомьтесь с документацией и кодом реализации android_native_app_glue .
Для обработки событий ввода получите ссылку на android_input_buffer с помощью android_app_swap_input_buffers() в игровом цикле. Эти буферы содержат события движения и события нажатия клавиш , произошедшие с момента последнего опроса. Количество содержащихся в них событий хранится в motionEventsCount и keyEventsCount соответственно.
Обрабатывайте каждое событие в игровом цикле. В этом примере следующий код перебирает
motionEventsи обрабатывает их с помощьюhandle_event:android_input_buffer* inputBuffer = android_app_swap_input_buffers(app); if (inputBuffer && inputBuffer->motionEventsCount) { for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) { GameActivityMotionEvent* motionEvent = &inputBuffer->motionEvents[i]; if (motionEvent->pointerCount > 0) { const int action = motionEvent->action; const int actionMasked = action & AMOTION_EVENT_ACTION_MASK; // Initialize pointerIndex to the max size, we only cook an // event at the end of the function if pointerIndex is set to a valid index range uint32_t pointerIndex = GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT; struct CookedEvent ev; memset(&ev, 0, sizeof(ev)); ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN; if (ev.motionIsOnScreen) { // use screen size as the motion range ev.motionMinX = 0.0f; ev.motionMaxX = SceneManager::GetInstance()->GetScreenWidth(); ev.motionMinY = 0.0f; ev.motionMaxY = SceneManager::GetInstance()->GetScreenHeight(); } switch (actionMasked) { case AMOTION_EVENT_ACTION_DOWN: pointerIndex = 0; ev.type = COOKED_EVENT_TYPE_POINTER_DOWN; break; case AMOTION_EVENT_ACTION_POINTER_DOWN: pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); ev.type = COOKED_EVENT_TYPE_POINTER_DOWN; break; case AMOTION_EVENT_ACTION_UP: pointerIndex = 0; ev.type = COOKED_EVENT_TYPE_POINTER_UP; break; case AMOTION_EVENT_ACTION_POINTER_UP: pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); ev.type = COOKED_EVENT_TYPE_POINTER_UP; break; case AMOTION_EVENT_ACTION_MOVE: { // Move includes all active pointers, so loop and process them here, // we do not set pointerIndex since we are cooking the events in // this loop rather than at the bottom of the function ev.type = COOKED_EVENT_TYPE_POINTER_MOVE; for (uint32_t i = 0; i < motionEvent->pointerCount; ++i) { _cookEventForPointerIndex(motionEvent, callback, ev, i); } break; } default: break; } // Only cook an event if we set the pointerIndex to a valid range, note that // move events cook above in the switch statement. if (pointerIndex != GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT) { _cookEventForPointerIndex(motionEvent, callback, ev, pointerIndex); } } } android_app_clear_motion_events(inputBuffer); }Пример реализации функции
_cookEventForPointerIndex()и других связанных функций можно найти на GitHub.Когда закончите, не забудьте очистить очередь событий, которые вы только что обработали:
android_app_clear_motion_events(mApp);
Дополнительные ресурсы
Чтобы узнать больше об GameActivity , см. следующие материалы:
- Примечания к выпуску GameActivity и AGDK .
- Используйте GameTextInput в GameActivity .
- Руководство по миграции NativeActivity .
- Справочная документация GameActivity .
- Реализация GameActivity .
Чтобы сообщить об ошибках или запросить новые функции в GameActivity, используйте систему отслеживания ошибок GameActivity .