Inizia a utilizzare GameActivity Componente del Android Game Development Kit.

Questa guida descrive come configurare e integrare GameActivity e come gestire gli eventi nel tuo gioco Android.

GameActivity ti consente di portare il tuo gioco C o C++ su Android semplificando il processo di utilizzo delle API fondamentali. In precedenza, NativeActivity era la classe consigliata per i giochi. GameActivity la sostituisce come classe consigliata per i giochi ed è compatibile con le versioni precedenti del livello API 19.

Per un esempio che integra GameActivity, consulta il repository dei giochi-samples.

Prima di iniziare

Vedi le release di GameActivity per ottenere una distribuzione.

Configura la build

Su Android, l'elemento Activity funge da punto di ingresso per il gioco e fornisce anche Window da utilizzare. Molti giochi estendono questo Activity con la propria classe Java o Kotlin per superare i limiti in NativeActivity utilizzando il codice JNI per connettersi al codice di gioco C o C++.

GameActivity offre le seguenti funzionalità:

GameActivity viene distribuito come Archivio Android (AAR). Questo AAR contiene la classe Java che utilizzi in AndroidManifest.xml, nonché il codice sorgente C e C++ che collega il lato Java di GameActivity all'implementazione C/C++ dell'app. Se utilizzi GameActivity 1.2.2 o versioni successive, viene fornita anche la libreria statica C/C++. Se applicabile, ti consigliamo di usare la libreria statica anziché il codice sorgente.

Includi questi file di origine o la libreria statica nel processo di compilazione tramite Prefab, che espone le librerie native e il codice sorgente al progetto CMake o alla compilazione NDK.

  1. Segui le istruzioni nella pagina Jetpack Android Games per aggiungere la dipendenza della libreria GameActivity al file build.gradle del tuo gioco.

  2. Attiva il prefabbricato procedendo nel seguente modo con la versione del plug-in per Android (AGP) 4.1 o versioni successive:

    • Aggiungi quanto segue al blocco android del file build.gradle del modulo:
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    Se utilizzi versioni precedenti di AGP, segui le istruzioni di configurazione corrispondenti nella documentazione prefabbricata.

  3. Importa la libreria statica C/C++ o il codice sorgente C/++ nel progetto come indicato di seguito.

    Libreria statica

    Nel file CMakeLists.txt del tuo progetto, importa la libreria statica game-activity nel modulo prefabbricato game-activity_static:

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

    Codice sorgente

    Nel file CMakeLists.txt del progetto, importa il pacchetto game-activity e aggiungilo alla destinazione. Il pacchetto game-activity richiede libandroid.so, quindi se non è presente, devi importarlo anche.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    

    Inoltre, includi i seguenti file in CmakeLists.txt del progetto: GameActivity.cpp, GameTextInput.cpp e android_native_app_glue.c.

In che modo Android lancia le tue attività

Il sistema Android esegue il codice nell'istanza Activity richiamando metodi di callback che corrispondono a fasi specifiche del ciclo di vita dell'attività. Per consentire ad Android di avviare la tua attività e avviare il gioco, devi dichiarare la tua attività con gli attributi appropriati nel file manifest Android. Per ulteriori informazioni, consulta la sezione Introduzione alle attività.

Manifest Android

Ogni progetto di app deve avere un file AndroidManifest.xml nella directory principale del set di origine del progetto. Il file manifest descrive le informazioni essenziali sulla tua app per gli strumenti di creazione di Android, il sistema operativo Android e Google Play. Ecco alcuni esempi:

Implementa GameActivity nel tuo gioco

  1. Crea o identifica la classe Java dell'attività principale (quella specificata nell'elemento activity all'interno del file AndroidManifest.xml). Modifica questa classe per estendere GameActivity dal pacchetto com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Assicurati che la tua libreria nativa sia caricata all'inizio utilizzando un blocco statico:

    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");
      }
      ...
    }
    
  3. Aggiungi la tua libreria nativa a AndroidManifest.xml se il nome della libreria non è il nome predefinito (libmain.so):

    <meta-data android:name="android.app.lib_name"
     android:value="android-game" />
    

Implementare android_main

  1. La libreria android_native_app_glue è una libreria di codice sorgente utilizzata dal gioco per gestire gli eventi del ciclo di vita di GameActivity in un thread separato al fine di evitare il blocco nel thread principale. Quando usi la libreria, registri il callback per gestire gli eventi del ciclo di vita, come gli eventi di input tocco. L'archivio GameActivity include la propria versione della libreria android_native_app_glue, quindi non puoi utilizzare la versione inclusa nelle release NDK. Se i tuoi giochi usano la raccolta android_native_app_glue inclusa nell'NDK, passa alla versione GameActivity.

    Dopo aver aggiunto il codice sorgente della libreria android_native_app_glue al progetto, il progetto si interfaccia con GameActivity. Implementa una funzione denominata android_main, che viene chiamata dalla libreria e utilizzata come punto di ingresso del gioco. Viene passato una struttura denominata android_app. Questa impostazione potrebbe variare in base al gioco e al motore. Ecco un esempio:

    #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;
    }
    
  2. Elabora android_app nel ciclo di gioco principale, ad esempio eseguendo il polling e la gestione degli eventi del ciclo dell'app definiti in nativeAppGlueAppCmd. Ad esempio, lo snippet seguente registra la funzione _hand_cmd_proxy come gestore NativeAppGlueAppCmd, poi esegue il polling degli eventi del ciclo dell'app e li invia al gestore registrato(in android_app::onAppCmd) per l'elaborazione:

    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();
        }
      }
    }
    
  3. Per ulteriori letture, analizza l'implementazione dell'esempio di NK Endless Tunnel. La differenza principale sarà il modo in cui gestire gli eventi, come mostrato nella prossima sezione.

Gestire gli eventi

Per consentire agli eventi di input di raggiungere la tua app, crea e registra i filtri eventi con android_app_set_motion_event_filter e android_app_set_key_event_filter. Per impostazione predefinita, la libreria native_app_glue consente solo gli eventi di movimento provenienti dall'input SOURCE_TOUCHSCREEN. Assicurati di consultare il documento di riferimento e il codice di implementazione android_native_app_glue per i dettagli.

Per gestire gli eventi di input, ottieni un riferimento a android_input_buffer con android_app_swap_input_buffers() nel tuo ciclo di gioco. Questi ultimi contengono gli eventi di movimento e gli eventi chiave che si sono verificati dall'ultima volta in cui è stato eseguito il sondaggio. Il numero di eventi contenuti è archiviato rispettivamente in motionEventsCount e keyEventsCount.

  1. Ripeti e gestisci ogni evento nel ciclo di gioco. In questo esempio, il seguente codice esegue l'iterazione di motionEvents e le gestisce tramite 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);
    }
    

    Consulta l'esempio di GitHub per l'implementazione di _cookEventForPointerIndex() e altre funzioni correlate.

  2. Al termine, ricordati di cancellare la coda degli eventi che hai appena gestito:

    android_app_clear_motion_events(mApp);
    

Risorse aggiuntive

Per saperne di più su GameActivity, consulta le seguenti risorse:

Per segnalare bug o richiedere nuove funzionalità a GameActivity, utilizza l'Issue Tracker di GameActivity.