Cómo migrar desde NativeActivity   Parte de Android Game Development Kit.

GameActivity se basa en NativeActivity del framework de Android e incluye mejoras y funciones nuevas:

  • Es compatible con Fragment de Jetpack.
  • Agrega compatibilidad con TextInput para facilitar la integración del teclado en pantalla.
  • Controla eventos táctiles y de teclas en la clase GameActivity de Java, en lugar de la interfaz NativeActivity onInputEvent.

La migración de NativeActivity a GameActivity es relativamente simple y requiere los siguientes cambios en la aplicación:

  1. Actualiza tus secuencias de comandos de compilación de Java y C/C++.
  2. Integra la nueva clase de Java y los códigos fuente de C/C++.

En el resto de esta guía, se abordan los pasos necesarios en detalle. Antes de realizar la migración, te recomendamos que leas la guía de introducción para obtener información de alto nivel sobre el funcionamiento de GameActivity.

Actualizaciones de la secuencia de comandos de compilación de Java

GameActivity se distribuye como una biblioteca de Jetpack. Asegúrate de aplicar los pasos de actualización de la secuencia de comandos de Gradle que se describen en la guía de introducción:

  1. Habilita la biblioteca de Jetpack en el archivo gradle.properties de tu proyecto:

    android.useAndroidX=true
    
  2. De manera opcional, especifica una versión de Prefab en el mismo archivo gradle.properties, por ejemplo:

    android.prefabVersion=2.0.0
    
  3. Habilita la función de Prefab en el archivo build.gradle de tu app:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. Agrega la dependencia GameActivity a tu aplicación:

    1. Agrega las bibliotecas core y games-activity.
    2. Si tu nivel mínimo actual de API compatible es anterior a 16, actualízalo al menos al 16.
    3. Actualiza la versión del SDK compilada a la que requiere la biblioteca games-activity. Jetpack suele requerir la versión más reciente del SDK en el momento de compilación del lanzamiento.

    El archivo build.gradle actualizado podría verse de la siguiente manera:

    android {
        compiledSdkVersion 31
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.4.2'
        implementation 'androidx.games:games-activity:1.1.0'
    }
    

Actualizaciones de código Kotlin o Java

NativeActivity se puede usar como una actividad de inicio y crear una aplicación de pantalla completa. En la actualidad, no se puede usar GameActivity como la actividad de inicio. Las apps deben derivar una clase de GameActivity y usarla como actividad de inicio. También debes realizar cambios adicionales de configuración para crear una app de pantalla completa.

En los siguientes pasos, se da por sentado que tu aplicación usa NativeActivity como la actividad de inicio. Si este no es el caso, puedes omitir la mayoría de ellos.

  1. Crea un archivo de Kotlin o Java para alojar la actividad nueva de inicio. Por ejemplo, el siguiente código crea la MainActivity como la actividad de inicio y carga la biblioteca nativa principal de la aplicación, 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. Crea un tema de app en pantalla completa en el archivo 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. Aplica el tema a la aplicación en el archivo AndroidManifest.xml:

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

    A fin de obtener instrucciones detalladas para el modo de pantalla completa, consulta la guía completa y la implementación de ejemplo en el repositorio de muestras de juegos.

Esta guía de migración no cambia el nombre de la biblioteca nativa. Si no lo cambias, asegúrate de que los nombres de las bibliotecas nativas sean consistentes en las siguientes tres ubicaciones:

  • Código Kotlin o Java:

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

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • Dentro del archivo de secuencia de comandos de compilación de C/C++, por ejemplo CMakeLists.txt:

    add_library(AndroidGame ...)
    

Actualizaciones de la secuencia de comandos de compilación de C/C++

En las instrucciones de esta sección, se usa cmake como ejemplo. Si tu aplicación usa ndk-build, debes asignarlas a los comandos equivalentes descritos en la página de documentación de ndk-build.

Actualmente, la implementación de C/C++ de GameActivity es solo de lanzamiento, empaquetada dentro del AAR con la utilidad prefab. El código nativo incluye las fuentes C/C++ de GameActivity y el código native_app_glue. Deben compilarse junto con el código C/C++ de la aplicación.

Las aplicaciones NativeActivity ya usan el código native_app_glue enviado que se incluye en el NDK. Debes reemplazarlo por la versión de native_app_glue de GameActivity. Aparte de esto, se aplican todos los pasos de cmake documentados en la guía de introducción:

  • Agrega game-activity a la dependencia del módulo C/C++ de la aplicación:

    find_package(game-activity REQUIRED CONFIG)
        ...
    target_link_libraries(AndroidGame game-activity::game-activity)
    
  • Quita todas las referencias al código native_app_glue del NDK, por ejemplo:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • Incluye los archivos de origen de 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)
    

Actualizaciones del código fuente de C/C++

Sigue estos pasos para reemplazar las referencias de NativeActivity en tu aplicación por GameActivity:

  • Usa el native_app_glue lanzado con GameActivity. Busca y reemplaza todo el uso de android_native_app_glue.h por lo siguiente:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • Configura el filtro de evento de movimiento y el filtro de evento de tecla en NULL para que tu app pueda recibir eventos de entrada de todos los dispositivos. Por lo general, debes hacerlo dentro de la función 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.
    }
    
  • Quita el código relacionado con AInputEvent y reemplázalo por la implementación de InputBuffer de 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;
    }
    
  • Revisa y actualiza la lógica que se adjuntó a AInputEvent de NativeActivity. Como se muestra en el paso anterior, el procesamiento de InputBuffer de GameActivity se encuentra fuera del bucle ALooper_pollAll().

  • Reemplaza el uso de android_app::activity->clazz por android_app:: activity->javaGameActivity. GameActivity cambia el nombre de la instancia GameActivity de Java.

Pasos adicionales

Los pasos anteriores abarcan las funciones de NativeActivity, pero GameActivity tiene funciones adicionales que tal vez quieras usar:

Te recomendamos que explores estas funciones y las adoptes según corresponda para tus juegos.

Si tienes alguna pregunta o recomendación para GameActivity, o bien para otras bibliotecas de AGDK, crea un error para informarnos.