Начните работу с 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.Предлагает API C, аналогичный
NativeActivity
, и библиотекуandroid_native_app_glue
.
GameActivity
распространяется в виде Android Archive (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 }
- Выберите версию Prefab и установите ее в файл
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 запускает вашу активность
Система Android выполняет код в вашем экземпляре Activity, вызывая методы обратного вызова, соответствующие определенным этапам жизненного цикла действия. Чтобы Android мог запустить вашу активность и запустить игру, вам необходимо объявить свою активность с соответствующими атрибутами в манифесте Android. Для получения дополнительной информации см. Введение в действия .
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_pollAll(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); }
См. пример GitHub для реализации
_cookEventForPointerIndex()
и других связанных функций.Когда вы закончите, не забудьте очистить очередь событий, которые вы только что обработали:
android_app_clear_motion_events(mApp);
Дополнительные ресурсы
Чтобы узнать больше о GameActivity
, см. следующее:
- Примечания к выпуску GameActivity и AGDK .
- Используйте GameTextInput в GameActivity .
- Руководство по миграции NativeActivity .
- Справочная документация GameActivity .
- Реализация GameActivity .
Чтобы сообщить об ошибках или запросить новые функции в GameActivity, используйте систему отслеживания проблем GameActivity .