Inizia a utilizzare GameActivity Componente del Game Development Kit di Android.
Questa guida descrive come configurare e integrare GameActivity
e come gestire gli eventi nel tuo gioco Android.
GameActivity
ti aiuta a 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 al livello API 19.
Per un esempio che integra GameActivity, consulta il repository di giochi-samples.
Prima di iniziare
Consulta la pagina 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 la Window
da utilizzare per disegnare. Molti giochi estendono questo Activity
con una propria classe Java o Kotlin per superare le limitazioni in NativeActivity
utilizzando il codice JNI
per passare al codice di gioco C o C++.
GameActivity
offre le seguenti funzionalità:
Eredita da
AppCompatActivity
, consentendoti di utilizzare i componenti dell'architettura Android Jetpack.Viene visualizzato in un elemento
SurfaceView
che ti consente di interfacciarsi con qualsiasi altro elemento dell'interfaccia utente Android.Gestisce gli eventi di attività Java. In questo modo qualsiasi elemento della UI Android (come
EditText
,WebView
oAd
) può essere integrato nel tuo gioco tramite un'interfaccia C.Offre un'API C simile a
NativeActivity
e una libreriaandroid_native_app_glue
.
GameActivity
viene distribuito come archivio Android
(AAR). Questo AAR contiene la classe Java
che utilizzi nel tuo
AndroidManifest.xml
, nonché il codice sorgente C
e C++ che connette 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, consigliamo di usare
la libreria statica anziché il codice sorgente.
Includi questi file sorgente o la libreria statica come parte del processo di compilazione tramite Prefab
, che espone le librerie native e il codice sorgente al tuo progetto CMake o alla build NDK.
Segui le istruzioni nella pagina Giochi Android Jetpack per aggiungere la dipendenza della libreria
GameActivity
al filebuild.gradle
del tuo gioco.Attiva il prefabbricato procedendo nel seguente modo con la versione del plug-in Android (AGP) 4.1 o versioni successive:
- Aggiungi quanto segue al blocco
android
del filebuild.gradle
del modulo:
buildFeatures { prefab true }
- Seleziona una versione prefabbricata
e impostala sul file
gradle.properties
:
android.prefabVersion=2.0.0
Se utilizzi versioni precedenti di AGP, segui la documentazione prefabbricata per le istruzioni di configurazione corrispondenti.
- Aggiungi quanto segue al blocco
Importa la libreria statica C/C++ o il codice sorgente di C/++ nel progetto nel seguente modo.
Libreria statica
Nel file
CMakeLists.txt
del tuo progetto, importa la libreria staticagame-activity
nel modulo prefabbricatogame-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 pacchettogame-activity
e aggiungilo alla destinazione. Il pacchettogame-activity
richiedelibandroid.so
, quindi, se non è presente, devi importarlo.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
Inoltre, includi i seguenti file nell'elemento
CmakeLists.txt
del progetto:GameActivity.cpp
,GameTextInput.cpp
eandroid_native_app_glue.c
.
In che modo Android avvia la tua attività
Il sistema Android esegue il codice nella tua 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 origini del progetto. Il file manifest descrive le informazioni essenziali sulla tua app per gli strumenti di build Android, il sistema operativo Android e Google Play. Alcuni esempi sono:
Nome del pacchetto e ID app per identificare in modo univoco il tuo gioco su Google Play.
Componenti dell'app, ad esempio attività, servizi, ricevitori di trasmissioni e fornitori di contenuti.
Autorizzazioni per accedere a parti protette del sistema o ad altre app.
Compatibilità dei dispositivi per specificare i requisiti hardware e software del tuo gioco.
Nome della libreria nativa per
GameActivity
eNativeActivity
(il valore predefinito è libmain.so).
Implementa GameActivity nel gioco
Crea o identifica la tua classe Java dell'attività principale (quella specificata nell'elemento
activity
all'interno del fileAndroidManifest.xml
). Cambia questo corso per estendereGameActivity
dal pacchettocom.google.androidgamesdk
:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
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"); } ... }
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
La libreria
android_native_app_glue
è una libreria di codice sorgente che il tuo gioco utilizza per gestire gli eventi del ciclo di vita diGameActivity
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, ad esempio gli eventi di input tocco. L'archivioGameActivity
include la propria versione della libreriaandroid_native_app_glue
, quindi non puoi utilizzare la versione inclusa nelle release NDK. Se i tuoi giochi usano la raccoltaandroid_native_app_glue
inclusa nell'NDK, passa alla versioneGameActivity
.Dopo aver aggiunto il codice sorgente della libreria
android_native_app_glue
al progetto, il progetto si interfaccia conGameActivity
. Implementa una funzione denominataandroid_main
, che viene richiamata dalla libreria e utilizzata come punto di ingresso per il tuo gioco. Viene passata una struttura chiamataandroid_app
. Questa opzione 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; }
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 gestoreNativeAppGlueAppCmd
, poi esegue il polling degli eventi di ciclo dell'app e li invia al gestore registrato(inandroid_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(); } } }
Per saperne di più, consulta l'esempio di Endless Tunnel NDK. La differenza principale sarà il modo in cui gestire gli eventi, come mostrato nella sezione successiva.
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 dall'input SOURCE_TOUCHSCREEN. Assicurati di consultare la documentazione di riferimento e il codice di implmenetazione di 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. che contengono eventi di movimento ed 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
.
Ripeti e gestisci ogni evento nel ciclo di gioco. In questo esempio, il seguente codice esegue l'iterazione di
motionEvents
e le gestisce tramitehandle_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.Al termine, ricordati di cancellare la coda degli eventi che hai appena gestito:
android_app_clear_motion_events(mApp);
Risorse aggiuntive
Per scoprire di più su GameActivity
, consulta le seguenti risorse:
- Note di rilascio di GameActivity e AGDK.
- Utilizza GameTextInput in GameActivity.
- Guida alla migrazione NativeActivity.
- Documentazione di riferimento di GameActivity.
- Implementazione di GameActivity.
Per segnalare bug o richiedere nuove funzionalità a GameActivity, utilizza il tracker dei problemi GameActivity.