Migracja z NativeActivity   Część Android Game Development Kit.

Z tej strony dowiesz się, jak przejść z wersji NativeActivity na wersję GameActivity w projekcie gry na Androida.

GameActivity opiera się na NativeActivity z ramy Androida, z dodatkowymi ulepszeniami i nowymi funkcjami:

  • Obsługuje Fragment z Jetpacka.
  • Dodano obsługę TextInput, aby ułatwić integrację z klawiaturą wirtualną.
  • Obsługuje zdarzenia dotyku i klawiszy w klasie Java GameActivity, a nie w interfejsie NativeActivity onInputEvent.

Przed migracją zalecamy zapoznanie się z przewodnikiem startowym, który zawiera informacje o konfigurowaniu i integrowaniu GameActivity w projekcie.

Aktualizacje skryptu kompilacji Java

GameActivity jest rozpowszechniana jako biblioteka Jetpack. Wykonaj czynności związane z aktualizacją skryptu Gradle opisane w przewodniku wprowadzającym:

  1. Włącz bibliotekę Jetpack w pliku gradle.properties projektu:

    android.useAndroidX=true
    
  2. Opcjonalnie możesz podać wersję Prefab w tym samym pliku gradle.properties, na przykład:

    android.prefabVersion=2.0.0
    
  3. Włącz funkcję Prefab w pliku build.gradle aplikacji:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. Dodaj do aplikacji zależność GameActivity:

    1. Dodaj biblioteki coregames-activity.
    2. Jeśli Twój obecny minimalny obsługiwany poziom interfejsu API jest mniejszy niż 16, zaktualizuj go do co najmniej 16.
    3. Zaktualizuj skompilowaną wersję pakietu SDK do tej, której wymaga biblioteka games-activity. Jetpack zwykle wymaga najnowszej wersji pakietu SDK w momencie kompilacji wersji.

    Zaktualizowany plik build.gradle może wyglądać mniej więcej tak:

    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'
    }
    

aktualizacje kodu Kotlin lub Java;

NativeActivity może być używany jako aktywność uruchamiania i tworzyć aplikację pełnoekranową. Obecnie nie można używać GameActivity jako aktywności uruchamiającej. Aplikacje muszą wywodzić klasę z GameActivity i używać jej jako działania uruchamiającego. Musisz też wprowadzić dodatkowe zmiany w konfiguracji, aby utworzyć aplikację pełnoekranową.

W przypadku tych czynności przyjmujemy, że aplikacja używa aktywności NativeActivity jako aktywności uruchamiającej. Jeśli tak nie jest, możesz pominąć większość z nich.

  1. Utwórz plik Kotlin lub Java, aby hostować nową aktywność uruchamiania. Na przykład ten kod tworzy aktywność MainActivity jako aktywność uruchamiania i wczytuje główną natywną bibliotekę aplikacji, libAndroidGame.so:

    Kotlin

    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")
           }
       }
    }

    Java

      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. Utwórz motyw aplikacji na pełnym ekranie w pliku 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. Zastosuj motyw do aplikacji w pliku AndroidManifest.xml:

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

    Szczegółowe instrukcje dotyczące trybu pełnoekranowego znajdziesz w przewodniku po immersji oraz w przykładowej implementacji w repozytorium z próbnymi grami.

Ten przewodnik po migracji nie zmienia nazwy natywnej biblioteki. Jeśli zdecydujesz się to zrobić, upewnij się, że nazwy bibliotek natywnych są spójne w tych 3 miejscach:

  • Kod Kotlin lub Java:

    System.loadLibrary(AndroidGame)
    
  • AndroidManifest.xml:

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • W pliku skryptu kompilacji C/C++, na przykład CMakeLists.txt:

    add_library(AndroidGame ...)
    

Aktualizacje skryptu kompilacji C/C++

W instrukcjach w tej sekcji jako przykład użyto cmake. Jeśli Twoja aplikacja używa polecenia ndk-build, musisz je zmapować na równe mu polecenia opisane na stronie dokumentacji ndk-build.

Implementacja C/C++ w GameActivity udostępnia wersję kodu źródłowego. W wersji 1.2.2 i nowszych udostępniana jest wersja statycznej biblioteki. Biblioteka statyczna jest zalecanym typem wersji.

Wersja jest spakowana w pliku AAR za pomocą narzędzia prefab. Kod natywny obejmuje źródła kodu C/C++ GameActivity oraz kod native_app_glue. Należy je skompilować razem z kodem C/C++ aplikacji.

Aplikacje NativeActivity korzystają już z kodu native_app_gluedostarczanego w NDK. Musisz go zastąpić wersją native_app_glue z GameActivity. Poza tym obowiązują wszystkie kroki cmakeopisane w przewodniku dla początkujących:

  • W sposób podany niżej zaimportuj do projektu stałą bibliotekę C/C++ lub kod źródłowy C/C++.

    Biblioteka statyczna

    W pliku CMakeLists.txt projektu zaimportuj stałą bibliotekę game-activity do modułu gotowego game-activity_static:

    find_package(game-activity REQUIRED CONFIG)
    target_link_libraries(${PROJECT_NAME} PUBLIC log android
    game-activity::game-activity_static)
    

    Kod źródłowy

    W pliku CMakeLists.txt projektu zaimportuj pakiet game-activity i dodaj go do celu. Pakiet game-activity wymaga pakietu libandroid.so, więc jeśli go brakuje, musisz go też zaimportować.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  • Usuń wszystkie odwołania do kodu native_app_glue NDK, takie jak:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • Jeśli używasz wersji kodu źródłowego, dołącz pliki źródłowe GameActivity. W przeciwnym razie pomiń ten krok.

    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)
    

Rozwiązanie problemu UnsatisfiedLinkError

Jeśli w przypadku funkcji com.google.androidgamesdk.GameActivity.initializeNativeCode() wystąpi błąd UnsatsifiedLinkError, dodaj ten kod do pliku CMakeLists.txt:

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

Aktualizacje kodu źródłowego C/C++

Aby zastąpić w aplikacji odwołania do NativeActivity wartością GameActivity, wykonaj te czynności:

  • Użyj wersji native_app_glue wydanej z wersją GameActivity. Wyszukaj i zastąp wszystkie wystąpienia android_native_app_glue.h:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • Ustaw filtr zdarzeń związanych z ruchu i filtr zdarzeń kluczowych na NULL, aby aplikacja mogła odbierać zdarzenia wejściowe ze wszystkich urządzeń wejściowych. Zwykle robisz to w ramach funkcji 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.
    }
    
  • Usuń kod związany z AInputEvent i zastąp go implementacją InputBuffer z 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_pollOnce(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;
    }
    
  • Sprawdź i zaktualizuj logikę dołączoną do metody NativeActivityAInputEvent. Jak pokazano w poprzednim kroku, przetwarzanie InputBuffer w klasie GameActivity odbywa się poza pętlą ALooper_pollOnce().

  • Zastąp użycie android_app::activity->clazz użyciem android_app:: activity->javaGameActivity. GameActivity zmienia nazwę instancji Java GameActivity.

Dodatkowe czynności

Poprzednie kroki obejmują funkcje klasy NativeActivity, ale GameActivity ma dodatkowe funkcje, których możesz użyć:

Zalecamy zapoznanie się z tymi funkcjami i zastosowanie ich w odpowiednich miejscach w grze.

Jeśli masz pytania lub rekomendacje dotyczące GameActivity lub innych bibliotek AGDK, utwórz błąd, aby poinformować nas o tym.