Migrar da NativeActivity Parte do Android Game Development Kit

Esta página descreve como migrar de NativeActivity para GameActivity no seu projeto de jogo Android.

A GameActivity tem como base a NativeActivity do framework do Android, com melhorias e novos recursos:

  • Oferece suporte ao Fragment do Jetpack.
  • Oferece suporte à TextInput, para facilitar a integração do teclado de software.
  • Processa eventos de toque e pressionamentos de tecla na classe Java GameActivity, e não na da interface onInputEvent da NativeActivity.

Antes de migrar, recomendamos a leitura do guia de iniciação, que descreve como configurar e integrar a GameActivity ao projeto.

Atualizações de scripts de build Java

GameActivity é distribuída como uma biblioteca do Jetpack. Siga as etapas de atualização do script do Gradle descritas no guia para iniciantes:

  1. Ative a biblioteca Jetpack no arquivo gradle.properties do projeto:

    android.useAndroidX=true
    
  2. Também é possível especificar uma versão do Prefab no mesmo arquivo gradle.properties, por exemplo:

    android.prefabVersion=2.0.0
    
  3. Ative o recurso Prefab no arquivo build.gradle do app:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. Adicione a dependência GameActivity ao app:

    1. Adicione as bibliotecas core e games-activity.
    2. Se o nível mínimo da API com suporte for inferior ao 16, atualize-o para o 16 ou mais recente.
    3. Atualize a versão compilada do SDK para a versão exigida pela biblioteca games-activity. O Jetpack normalmente exige a versão mais recente do SDK no momento de lançamento do build.

    O arquivo build.gradle atualizado vai ficar assim:

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

Atualizações de código Kotlin ou Java

A NativeActivity pode ser usada como uma atividade de inicialização, que abre um app em tela cheia. No momento, a GameActivity não pode ser usada como a atividade de inicialização. Os apps precisam derivar uma classe de GameActivity e usá-la para a inicialização. Também é necessário fazer outras mudanças de configuração para criar um app que vai ser mostrado em tela cheia.

Nas etapas abaixo, presumimos que o app usa NativeActivity como a atividade de inicialização. Se esse não for o caso, você pode pular a maioria dessas etapas.

  1. Crie um arquivo Kotlin ou Java para hospedar a nova atividade de inicialização. Por exemplo, o código abaixo cria a MainActivity como atividade de inicialização e carrega a biblioteca nativa principal do app, 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. Crie um tema de app em tela cheia no arquivo 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. Aplique esse tema ao app no arquivo AndroidManifest.xml:

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

    Para ver instruções detalhadas sobre o modo de tela cheia, consulte o guia de imersão e confira um exemplo de implementação no repositório games-samples (em inglês).

Nas etapas apresentadas neste guia de migração, não mudamos o nome da biblioteca nativa. Caso você faça essa mudança, confirme se os nomes das bibliotecas nativas estão consistentes nestes três locais:

  • Código Kotlin ou Java:

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

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • No arquivo do script de build C/C++, por exemplo, CMakeLists.txt:

    add_library(AndroidGame ...)
    

Atualizações de scripts de build C/C++

As instruções nesta seção usam cmake como exemplo. Caso seu app use o ndk-build, considere os comandos equivalentes descritos na página de documentação do ndk-build.

A implementação C/C++ da GameActivity oferece uma versão do código-fonte. Para a versão 1.2.2 e mais recentes, é fornecida uma versão de biblioteca estática. A biblioteca estática é o tipo de versão recomendado.

A versão é incluída no AAR com o utilitário prefab. O código nativo inclui as origens C/C++ da GameActivity e o código native_app_glue. Eles precisam ser criados junto ao código C/C++ do app.

Apps com a NativeActivity já usam o código native_app_glue incluído no NDK. No entanto, é necessário substituir esse elemento pela versão de native_app_glue da GameActivity. Além disso, todas as etapas cmake documentadas no guia de introdução também são válidas:

  • Importe a biblioteca estática C/C++ ou o código-fonte C/++ para seu projeto da seguinte maneira:

    Biblioteca estática

    No arquivo CMakeLists.txt do projeto, importe a biblioteca estática game-activity para o módulo prefab game-activity_static:

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

    Código-fonte

    No arquivo CMakeLists.txt do projeto, importe o pacote game-activity e adicione-o ao destino: O pacote game-activity requer libandroid.so. Portanto, se ele estiver ausente, será necessário importá-lo.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  • Remova todas as referências ao código native_app_glue do NDK, como:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • Se você estiver usando a versão do código-fonte, inclua os arquivos de origem GameActivity. Caso contrário, pule esta etapa.

    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)
    

Resolver o problema UnsatisfiedLinkError

Se você encontrar um UnsatsifiedLinkError para a função com.google.androidgamesdk.GameActivity.initializeNativeCode(), adicione este código ao arquivo CMakeLists.txt:

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

Atualizações de código-fonte C/C++

Siga estas etapas para substituir referências NativeActivity no app por GameActivity:

  • Use o elemento native_app_glue lançado com GameActivity. Pesquise e substitua todas as ocorrências de android_native_app_glue.h por:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • Defina o filtro dos eventos de movimento e dos de tecla como NULL para que seu app possa receber eventos de todos os dispositivos de entrada. Normalmente, isso é feito dentro da função 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.
    }
    
  • Remova o código AInputEvent relacionado e o substitua pela implementação de InputBuffer da 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;
    }
    
  • Revise e atualize a lógica ligada ao AInputEvent da NativeActivity. Como mostrado na etapa anterior, o processamento de InputBuffer da GameActivity ocorre fora do loop ALooper_pollAll().

  • Substitua o uso de android_app::activity->clazz por android_app:: activity->javaGameActivity. A GameActivity renomeia a instância Java da GameActivity.

Outras etapas

As etapas anteriores abrangem as funções da NativeActivity, mas a GameActivity inclui outros recursos que você pode usar:

Recomendamos que você conheça esses recursos e os adote conforme necessário para seus jogos.

Caso você tenha dúvidas ou recomendações relacionadas à GameActivity ou outras bibliotecas do AGDK, crie um relatório de bug para nos contar sobre isso.