Premiers pas avec GameActivity composante de l'Android Game Development Kit
Ce guide explique comment configurer et intégrer GameActivity, et comment gérer les événements dans votre jeu Android.
GameActivity vous aide à transférer votre jeu C ou C++ sous Android en simplifiant le processus d'utilisation des API essentielles.
Auparavant, NativeActivity était la classe recommandée pour les jeux. GameActivity est désormais recommandée à sa place ; cette classe est rétrocompatible jusqu'au niveau 19 de l'API.
Pour un exemple intégrant GameActivity, accédez au dépôt games-samples.
Avant de commencer
Consultez les versions de GameActivity pour obtenir une distribution.
Configurer votre build
Sous Android, une Activity sert de point d'entrée à votre jeu et fournit la Window dans laquelle vous pouvez dessiner. De nombreux jeux étendent cette Activity avec leur propre classe Java ou Kotlin afin de contourner les limitations de NativeActivity tout en utilisant le code JNI pour faire le lien avec leur code de jeu C ou C++.
GameActivity offre les fonctionnalités suivantes :
Hérite de
AppCompatActivity, ce qui vous permet d'utiliser les composants d'architecture Android Jetpack.S'affiche dans une
SurfaceViewqui vous permet d'interagir avec tout autre élément d'interface utilisateur Android.Gère les événements d'activité Java. Cela permet d'intégrer n'importe quel élément d'interface utilisateur Android (tel que
EditText,WebViewouAd) à votre jeu via une interface C.Offre une API C semblable à
NativeActivity, et la bibliothèqueandroid_native_app_glue.
GameActivity est distribué sous forme d'archive Android (AAR). Cette AAR contient la classe Java que vous utilisez dans votre fichier AndroidManifest.xml, ainsi que le code source C et C++ qui connecte le côté Java de GameActivity à l'implémentation C/C++ de l'application. Si vous utilisez GameActivity 1.2.2 ou une version ultérieure, la bibliothèque statique C/C++ est également fournie. Le cas échéant, nous vous recommandons d'utiliser la bibliothèque statique plutôt que le code source.
Incluez ces fichiers sources ou la bibliothèque statique dans votre processus de compilation via Prefab, qui expose les bibliothèques natives et le code source au niveau de votre projet CMake ou de votre build NDK.
Suivez les instructions de la page Jetpack Jeux Android pour ajouter la dépendance de la bibliothèque
GameActivityau fichierbuild.gradlede votre jeu.Activez Prefab en procédant comme suit avec le plug-in Android (AGP) version 4.1 ou ultérieure :
- Ajoutez le code suivant au bloc
androiddu fichierbuild.gradlede votre module :
buildFeatures { prefab true }- Sélectionnez une version de Prefab et définissez-la dans le fichier
gradle.properties:
android.prefabVersion=2.0.0Si vous utilisez des versions antérieures du plug-in AGP, suivez la documentation de Prefab pour obtenir les instructions de configuration correspondantes.
- Ajoutez le code suivant au bloc
Importez la bibliothèque statique C/C++ ou le code source C/++ dans votre projet comme suit.
Bibliothèque statique
Dans le fichier
CMakeLists.txtde votre projet, importez la bibliothèque statiquegame-activitydans le module Prefabgame-activity_static:find_package(game-activity REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PUBLIC log android game-activity::game-activity_static)Code source
Dans le fichier
CMakeLists.txtde votre projet, importez le packagegame-activityet ajoutez-le à votre cible. Le packagegame-activitynécessitelibandroid.so. S'il est absent, vous devez donc également l'importer.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)Incluez également les fichiers suivants dans le fichier
CmakeLists.txtde votre projet :GameActivity.cpp,GameTextInput.cppetandroid_native_app_glue.c.
Comment Android lance-t-il votre instance Activity ?
Le système Android exécute le code dans votre instance Activity en appelant des méthodes de rappel qui correspondent à des étapes spécifiques du cycle de vie de l'activité. Pour qu'Android puisse lancer votre activité et démarrer votre jeu, vous devez déclarer l'activité avec les attributs appropriés dans le fichier manifeste Android. Pour en savoir plus, consultez Présentation des activités.
Fichier manifeste Android
Chaque projet d'application doit disposer d'un fichier AndroidManifest.xml à la racine de l'ensemble de sources du projet. Le fichier manifeste fournit aux outils de compilation Android, au système d'exploitation Android et à Google Play des informations essentielles sur votre application. Par exemple :
Nom du package et ID de l'application pour identifier de façon unique votre jeu sur Google Play
Composants d'application tels que les activités, les services, les broadcast receivers et les fournisseurs de contenu
Autorisations d'accès à des parties protégées du système ou à d'autres applis
Compatibilité des appareils afin de spécifier les exigences matérielles et logicielles de votre jeu
Nom de la bibliothèque native pour
GameActivityetNativeActivity(par défaut, libmain.so)
Implémenter GameActivity dans votre jeu
Créez ou identifiez la classe Java de votre activité principale (celle spécifiée dans l'élément
activityde votre fichierAndroidManifest.xml). Modifiez cette classe pour étendreGameActivityà partir du packagecom.google.androidgamesdk:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }Assurez-vous que votre bibliothèque native est chargée au démarrage à l'aide d'un bloc statique :
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"); } ... }Ajoutez votre bibliothèque native au fichier
AndroidManifest.xmlsi le nom de votre bibliothèque n'est pas le nom par défaut (libmain.so) :<meta-data android:name="android.app.lib_name" android:value="android-game" />
Implémenter android_main
La bibliothèque
android_native_app_glueest une bibliothèque de code source que votre jeu utilise pour gérer les événements de cycle de vieGameActivitydans un thread distinct afin d'éviter le blocage de votre thread principal. Lorsque vous utilisez la bibliothèque, vous enregistrez le rappel pour gérer les événements de cycle de vie, tels que les événements de saisie tactile. L'archiveGameActivitycomprend sa propre version de la bibliothèqueandroid_native_app_glue. Vous ne pouvez donc pas utiliser la version incluse dans les versions du NDK. Si vos jeux utilisent la bibliothèqueandroid_native_app_glueincluse dans le NDK, passez à la versionGameActivity.Une fois que vous avez ajouté le code source de la bibliothèque
android_native_app_glueà votre projet, elle interagit avecGameActivity. Implémentez une fonction appeléeandroid_main, qui est appelée par la bibliothèque et utilisée comme point d'entrée de votre jeu. Elle est transmise à une structure appeléeandroid_app. Cette structure peut varier selon votre jeu et votre moteur. Exemple :#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; }Traitez
android_appdans votre boucle de jeu principale, par exemple pour interroger et gérer les événements de cycle de vie d'application définis dans NativeAppGlueAppCmd. L'extrait de code suivant enregistre la fonction_hand_cmd_proxyen tant que gestionnaireNativeAppGlueAppCmd, puis interroge les événements de cycle de vie d'application et les envoie au gestionnaire enregistré (dansandroid_app::onAppCmd) pour traitement :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_pollOnce(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(); } } }Pour en savoir plus, étudiez l'implémentation de l'exemple Endless Tunnel du NDK. La principale différence réside dans la façon dont les événements sont gérés, comme indiqué dans la section suivante.
Gérer les événements
Pour que les événements d'entrée puissent accéder à votre application, créez et enregistrez vos filtres d'événements avec android_app_set_motion_event_filter et android_app_set_key_event_filter.
Par défaut, la bibliothèque native_app_glue n'autorise que les événements de mouvement provenant de l'entrée SOURCE_TOUCHSCREEN. Pour en savoir plus, consultez la documentation de référence et le code d'implémentation d'android_native_app_glue.
Pour gérer les événements d'entrée, obtenez une référence à android_input_buffer avec android_app_swap_input_buffers() dans votre boucle de jeu. Ceux-ci contiennent les événements de mouvement et les événements clés qui se sont produits depuis la dernière interrogation. Le nombre d'événements contenus est respectivement stocké dans motionEventsCount et keyEventsCount.
Itérez et gérez chacun des événements de votre boucle de jeu. Dans cet exemple, le code suivant itère les
motionEventset les gère viahandle_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); }Consultez l'exemple GitHub pour l'implémentation de
_cookEventForPointerIndex()et d'autres fonctions associées.Lorsque vous avez terminé, n'oubliez pas de vider la file d'attente des événements que vous venez de gérer :
android_app_clear_motion_events(mApp);
Ressources supplémentaires
Pour en savoir plus sur GameActivity, consultez :
- Notes de version de GameActivity et d'AGDK
- Utiliser GameTextInput dans GameActivity
- Guide de migration de NativeActivity
- Documentation de référence sur GameActivity
- Implémentation de GameActivity
Pour signaler des bugs ou demander de nouvelles fonctionnalités dans GameActivity, utilisez l'outil Issue Tracker de GameActivity.