Миграция с NativeActivity . Часть комплекта разработки игр для Android .

На этой странице описано, как перейти с NativeActivity на GameActivity в вашем игровом проекте Android.

GameActivity основан на NativeActivity из платформы Android с улучшениями и новыми функциями:

  • Поддерживает Fragment из Jetpack.
  • Добавляет поддержку TextInput для облегчения интеграции с программной клавиатурой.
  • Обрабатывает события касания и нажатия клавиш в Java-классе GameActivity , а не в интерфейсе NativeActivity onInputEvent .

Перед миграцией рекомендуем прочитать руководство по началу работы , в котором описано, как настроить и интегрировать GameActivity в ваш проект.

Обновления сценариев сборки Java

GameActivity распространяется как библиотека Jetpack. Обязательно примените шаги обновления сценария Gradle, описанные в руководстве по началу работы :

  1. Включите библиотеку Jetpack в файле gradle.properties вашего проекта:

    android.useAndroidX=true
  2. При желании укажите версию Prefab в том же файле gradle.properties , например:

    android.prefabVersion=2.0.0
  3. Включите функцию Prefab в файле build.gradle вашего приложения:

    android {
       
    ... // other configurations
        buildFeatures
    .prefab true
    }
  4. Добавьте зависимость GameActivity в свое приложение:

    1. Добавьте библиотеки core и games-activity .
    2. Если текущий минимальный поддерживаемый уровень API меньше 16, обновите его как минимум до 16.
    3. Обновите скомпилированную версию SDK до той, которая требуется библиотеке games-activity . Jetpack обычно требует последней версии SDK на момент сборки выпуска.

    Ваш обновленный файл build.gradle может выглядеть примерно так:

    android {
        compiledSdkVersion
    33
       
    ... // other configurations.
        defaultConfig
    {
            minSdkVersion
    16
       
    }
       
    ... // other configurations.

        buildFeatures
    .prefab true
    }
    dependencies
    {
        implementation
    'androidx.core:core:1.9.0'
        implementation
    'androidx.games:games-activity:1.2.2'
    }

Обновления кода Kotlin или Java

NativeActivity можно использовать в качестве активности запуска и создавать полноэкранное приложение. В настоящее время GameActivity нельзя использовать в качестве активности запуска. Приложения должны наследовать класс от GameActivity и использовать его в качестве активности запуска. Вам также необходимо внести дополнительные изменения в конфигурацию, чтобы создать полноэкранное приложение.

Следующие шаги предполагают, что ваше приложение использует NativeActivity в качестве действия запуска. Если это не так, вы можете пропустить большинство из них.

  1. Создайте файл Kotlin или Java для размещения нового действия при запуске. Например, следующий код создает MainActivity в качестве активности запуска и загружает основную собственную библиотеку приложения libAndroidGame.so :

    class MainActivity : GameActivity() {
       
    override fun onResume() {
           
    super.onResume()
           
    // Use the function recommended from the following page:
           
    // https://d.android.com/training/system-ui/immersive
           hideSystemBars
    ()
       
    }
       
    companion object {
           
    init {
               
    System.loadLibrary("AndroidGame")
           
    }
       
    }
    }
      public class MainActivity extends GameActivity {
         
    protected void onResume() {
             
    super.onResume();
             
    // Use the function recommended from
             
    // https://d.android.com/training/system-ui/immersive
              hideSystemBars
    ();
         
    }
         
    static {
             
    System.loadLibrary("AndroidGame");
         
    }
     
    }
  2. Создайте тему полноэкранного приложения в файле res\values\themes.xml :

    <resources xmlns:tools="http://schemas.android.com/tools">
       
    <!-- Base application theme. -->
       
    <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar">
           
    <item name="android:windowFullscreen">true</item>
           
    <item name="android:windowContentOverlay">@null</item>"
       
    </style>
    </resources>
  3. Примените тему к приложению в файле AndroidManifest.xml :

    <application  android:theme=”@style/Application.Fullscreen”>
         
    <!-- other configurations not listed here. -->
    </application>

    Подробные инструкции по полноэкранному режиму смотрите в иммерсивном руководстве и примере реализации в репозитории games-samples .

Это руководство по миграции не меняет имя собственной библиотеки. Если вы все же измените его, убедитесь, что имена собственных библиотек согласованы в следующих трех местах:

  • Котлин или Java-код:

    System.loadLibrary(“AndroidGame”)
  • AndroidManifest.xml :

    <meta-data android:name="android.app.lib_name"
           
    android:value="AndroidGame" />
  • Внутри файла сценария сборки C/C++, например CMakeLists.txt :

    add_library(AndroidGame ...)

Обновления сценариев сборки C/C++

В инструкциях в этом разделе в качестве примера используется cmake . Если ваше приложение использует ndk-build , вам необходимо сопоставить их с эквивалентными командами, описанными на странице документации ndk-build .

Реализация GameActivity на C/C++ предоставила выпуск исходного кода. Для версии 1.2.2 и более поздних предусмотрен выпуск статической библиотеки. Статическая библиотека является рекомендуемым типом выпуска.

Релиз упакован в AAR с помощью prefab утилиты. Собственный код включает исходные коды C/C++ GameActivity и код native_app_glue . Их необходимо создавать вместе с кодом C/C++ вашего приложения.

Приложения NativeActivity уже используют код native_app_glue , поставляемый в составе NDK. Вы должны заменить его версией native_app_glue от GameActivity. Кроме этого, применимы все шаги cmake , описанные в руководстве по началу работы:

  • Импортируйте либо статическую библиотеку 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)
  • Удалите все ссылки на код native_app_glue NDK, например:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
       
    ...
    set
    (CMAKE_SHARED_LINKER_FLAGS
       
    "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
  • Если вы используете выпуск исходного кода, включите исходные файлы GameActivity . В противном случае пропустите этот шаг.

    get_target_property(game-activity-include
                        game
    -activity::game-activity
                        INTERFACE_INCLUDE_DIRECTORIES
    )
    add_library
    (${PROJECT_NAME} SHARED
        main
    .cpp
        $
    {game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c
        $
    {game-activity-include}/game-activity/GameActivity.cpp
        $
    {game-activity-include}/game-text-input/gametextinput.cpp)

Решение проблемы UnsatisfiedLinkError

Если вы столкнулись с UnsatsifiedLinkError для функции com.google.androidgamesdk.GameActivity.initializeNativeCode() , добавьте этот код в файл CMakeLists.txt :

set(CMAKE_SHARED_LINKER_FLAGS
   
"${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode"
)

Обновления исходного кода C/C++

Выполните следующие действия, чтобы заменить ссылки NativeActivity в вашем приложении на GameActivity :

  • Используйте native_app_glue выпущенный вместе с GameActivity . Найдите и замените все использование android_native_app_glue.h на:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
  • Установите для фильтра событий движения и фильтра событий клавиш значение NULL чтобы ваше приложение могло получать события ввода со всех устройств ввода. Обычно вы делаете это внутри функции android_main() :

    void android_main(android_app* app) {
       
    ... // other init code.

        android_app_set_key_event_filter
    (app, NULL);
        android_app_set_motion_event_filter
    (app, NULL);

       
    ... // additional init code, and game loop code.
    }
  • Удалите код, связанный с AInputEvent , и замените его реализацией InputBuffer GameActivity:

    while (true) {
       
    // Read all pending events.
       
    int events;
       
    struct android_poll_source* source;

       
    // If not animating, block forever waiting for events.
       
    // If animating, loop until all events are read, then continue
       
    // to draw the next frame of animation.
       
    while ((ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                               
    (void**)&source)) >= 0) {
           
    // Process this app cycle or inset change event.
           
    if (source) {
               source
    ->process(source->app, source);
           
    }

             
    ... // Other processing.

           
    // Check if app is exiting.
           
    if (state->destroyRequested) {
               engine_term_display
    (&engine);
               
    return;
           
    }
       
    }
       
    // Process input events if there are any.
        engine_handle_input
    (state);

       
    if (engine.animating) {
           
    // Draw a game frame.
       
    }
    }

    // Implement input event handling function.
    static int32_t engine_handle_input(struct android_app* app) {
       
    auto* engine = (struct engine*)app->userData;
       
    auto ib = android_app_swap_input_buffers(app);
       
    if (ib && ib->motionEventsCount) {
           
    for (int i = 0; i < ib->motionEventsCount; i++) {
               
    auto *event = &ib->motionEvents[i];
               int32_t ptrIdx
    = 0;
               
    switch (event->action & AMOTION_EVENT_ACTION_MASK) {
                   
    case AMOTION_EVENT_ACTION_POINTER_DOWN:
                   
    case AMOTION_EVENT_ACTION_POINTER_UP:
                       
    // Retrieve the index for the starting and the ending of any secondary pointers
                       ptrIdx
    = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
    ;
                   
    case AMOTION_EVENT_ACTION_DOWN:
                   
    case AMOTION_EVENT_ACTION_UP:
                       engine
    ->state.x = GameActivityPointerAxes_getAxisValue(
                           
    &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X);
                       engine
    ->state.y = GameActivityPointerAxes_getAxisValue(
                           
    &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y);
                       
    break;
                   
    case AMOTION_EVENT_ACTION_MOVE:
                   
    // Process the move action: the new coordinates for all active touch pointers
                   
    // are inside the event->pointers[]. Compare with our internally saved
                   
    // coordinates to find out which pointers are actually moved. Note that there is
                   
    // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there
                   
    // might be multiple pointers moved at the same time).
                       
    ...
                       
    break;
               
    }
           
    }
           android_app_clear_motion_events
    (ib);
       
    }

       
    // Process the KeyEvent in a similar way.
           
    ...

       
    return 0;
    }
  • Просмотрите и обновите логику, прикрепленную к AInputEvent NativeActivity. Как показано на предыдущем шаге, обработка InputBuffer GameActivity находится вне цикла ALooper_pollAll() .

  • Замените использование android_app::activity->clazz на android_app:: activity->javaGameActivity . GameActivity переименовывает экземпляр Java GameActivity .

Дополнительные шаги

Предыдущие шаги охватывают функциональность NativeActivity, но у GameActivity есть дополнительные функции, которые вы, возможно, захотите использовать:

Мы рекомендуем изучить эти функции и использовать их в своих играх.

Если у вас есть какие-либо вопросы или рекомендации по GameActivity или другим библиотекам AGDK, создайте сообщение об ошибке и сообщите нам об этом.