Начните работу с GameActivity , частью комплекта разработки игр для Android .
В этом руководстве описывается, как настроить и интегрировать 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 , чтобы добавить зависимость библиотеки
GameActivity
в файлbuild.gradle
вашей игры.Включите prefab, выполнив следующие действия с версией плагина Android (AGP) 4.1+ :
- Добавьте следующее в блок
android
файлаbuild.gradle
вашего модуля:
buildFeatures { prefab true }
- Выберите версию Prefab и установите ее в файле
gradle.properties
:
android.prefabVersion=2.0.0
Если вы используете более ранние версии AGP, следуйте инструкциям по настройке из предварительной документации .
- Добавьте следующее в блок
Импортируйте статическую библиотеку C/C++ или исходный код C/++ в свой проект следующим образом.
Статическая библиотека
В файле
CMakeLists.txt
вашего проекта импортируйте статическую библиотекуgame-activity
в модуль prefabgame-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. Подробнее см. в разделе «Введение в Activities» .
Манифест 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 .