Erste Schritte mit GameActivity Teil des Android Game Development Kit.

In diesem Leitfaden wird beschrieben, wie du GameActivity einrichtest und einbindest und Ereignisse in deinem Android-Spiel verarbeiten kannst.

GameActivity vereinfacht die Verwendung kritischer APIs und hilft dir dabei, dein C- oder C++-Spiel für Android zu nutzen. Bisher war NativeActivity die empfohlene Klasse für Spiele. GameActivity ersetzt sie als empfohlene Klasse für Spiele und ist abwärtskompatibel mit API-Level 19.

Ein Beispiel, in das GameActivity integriert wird, finden Sie im games-sample-Repository.

Vorbereitung

Informationen zum Abrufen einer Distribution finden Sie unter GameActivity-Releases.

Build einrichten

Unter Android dient ein Activity als Einstiegspunkt für Ihr Spiel und stellt auch den Window zum Zeichnen bereit. Viele Spiele erweitern diese Activity um eine eigene Java- oder Kotlin-Klasse, um Einschränkungen in NativeActivity zu umgehen, und verwenden JNI-Code, um eine Verbindung zu ihrem C- oder C++-Spielcode herzustellen.

GameActivity bietet die folgenden Funktionen:

GameActivity wird als Android Archive (AAR) bereitgestellt. Dieses AAR enthält die Java-Klasse, die Sie in AndroidManifest.xml verwenden, sowie den C- und C++-Quellcode, der die Java-Seite von GameActivity mit der C/C++-Implementierung der Anwendung verbindet. Wenn Sie GameActivity 1.2.2 oder höher verwenden, ist auch die statische C/C++-Bibliothek verfügbar. Wir empfehlen, nach Möglichkeit die statische Bibliothek anstelle des Quellcodes zu verwenden.

Binden Sie diese Quelldateien oder die statische Bibliothek als Teil des Build-Prozesses über Prefab ein. Dadurch werden native Bibliotheken und Quellcode in Ihrem CMake-Projekt oder NDK-Build verfügbar gemacht.

  1. Folgen Sie der Anleitung auf der Seite Jetpack Android Games, um die Abhängigkeit der GameActivity-Bibliothek in die Datei build.gradle Ihres Spiels einzufügen.

  2. Aktivieren Sie die Vorabfertigung, indem Sie mit der Android-Plug-in-Version (AGP) 4.1 oder höher Folgendes tun:

    • Fügen Sie Folgendes in den android-Block der Datei build.gradle Ihres Moduls ein:
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    Wenn Sie ältere AGP-Versionen verwenden, finden Sie die entsprechende Konfigurationsanleitung in der Prefab-Dokumentation.

  3. Importieren Sie entweder die statische C/C++-Bibliothek oder den C/++-Quellcode wie unten beschrieben in Ihr Projekt.

    Statische Bibliothek

    Importieren Sie in der Datei CMakeLists.txt Ihres Projekts die statische Bibliothek game-activity in das Fertigungsmodul game-activity_static:

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

    Quellcode

    Importieren Sie das Paket game-activity in die Datei CMakeLists.txt Ihres Projekts und fügen Sie es dem Ziel hinzu. Für das game-activity-Paket ist libandroid.so erforderlich. Wenn es fehlt, müssen Sie es also ebenfalls importieren.

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

    Fügen Sie außerdem die folgenden Dateien in die Datei CmakeLists.txt Ihres Projekts ein: GameActivity.cpp, GameTextInput.cpp und android_native_app_glue.c.

So startet Android deine Aktivitäten

Das Android-System führt Code in Ihrer Activity-Instanz aus, indem es Callback-Methoden aufruft, die bestimmten Phasen des Aktivitätslebenszyklus entsprechen. Damit Android deine Aktivitäten starten und dein Spiel starten kann, musst du deine Aktivitäten mit den entsprechenden Attributen im Android-Manifest deklarieren. Weitere Informationen finden Sie unter Einführung in Aktivitäten.

Android-Manifest

Für jedes App-Projekt muss die Datei AndroidManifest.xml im Stammverzeichnis des Projektquellsatzes vorhanden sein. Die Manifestdatei beschreibt wichtige Informationen zu deiner App für die Android-Build-Tools, das Android-Betriebssystem und Google Play. Hierzu zählen:

GameActivity in Ihrem Spiel implementieren

  1. Erstellen oder identifizieren Sie die Java-Hauptaktivitätsklasse, die im activity-Element der AndroidManifest.xml-Datei angegeben ist. Ändern Sie diese Klasse, um GameActivity aus dem com.google.androidgamesdk-Paket zu erweitern:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Achten Sie darauf, dass Ihre native Bibliothek zu Beginn mit einem statischen Block geladen wird:

    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. Fügen Sie Ihre native Bibliothek zu AndroidManifest.xml hinzu, wenn Ihr Bibliotheksname nicht dem Standardnamen (libmain.so) entspricht:

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

android_main implementieren

  1. Die android_native_app_glue-Bibliothek ist eine Quellcodebibliothek, mit der dein Spiel GameActivity-Lebenszyklusereignisse in einem separaten Thread verwaltet, um eine Blockierung in deinem Hauptthread zu verhindern. Wenn Sie die Bibliothek verwenden, registrieren Sie den Callback zur Verarbeitung von Lebenszyklusereignissen wie Touch-Eingaben. Das GameActivity-Archiv enthält eine eigene Version der android_native_app_glue-Bibliothek, sodass Sie nicht die in NDK-Releases enthaltene Version verwenden können. Wenn Ihre Spiele die android_native_app_glue-Bibliothek verwenden, die im NDK enthalten ist, wechseln Sie zur Version GameActivity.

    Nachdem Sie Ihrem Projekt den Quellcode der android_native_app_glue-Bibliothek hinzugefügt haben, interagiert es mit GameActivity. Implementieren Sie eine Funktion namens android_main, die von der Bibliothek aufgerufen und als Einstiegspunkt für Ihr Spiel verwendet wird. Es wird eine Struktur mit dem Namen android_app übergeben. Das kann je nach Spiel und Engine variieren. Beispiel:

    #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. Verarbeiten Sie android_app in Ihrer Hauptspielschleife, z. B. das Abfragen und Verarbeiten von App-Zyklusereignissen, die in NativeAppGlueAppCmd definiert sind. Das folgende Snippet registriert beispielsweise die Funktion _hand_cmd_proxy als NativeAppGlueAppCmd-Handler, fragt dann App-Zyklusereignisse ab und sendet sie zur Verarbeitung an den registrierten Handler(in android_app::onAppCmd):

    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. Weitere Informationen finden Sie in der Implementierung des NDK-Beispiels Endless Tunnel. Der Hauptunterschied besteht darin, wie mit Ereignissen umgegangen wird (siehe nächster Abschnitt).

Ereignisse verarbeiten

Damit Eingabeereignisse Ihre App erreichen, müssen Sie Ereignisfilter mit android_app_set_motion_event_filter und android_app_set_key_event_filter erstellen und registrieren. Standardmäßig lässt die native_app_glue-Bibliothek nur Bewegungsereignisse aus der SOURCE_TOUCHSCREEN-Eingabe zu. Weitere Informationen finden Sie in der Referenzdokumentation und im android_native_app_glue-Implementierungscode.

Rufe zum Verarbeiten von Eingabeereignissen einen Verweis auf android_input_buffer mit android_app_swap_input_buffers() in deiner Spielschleife ab. Sie enthalten Bewegungsereignisse und Schlüsselereignisse, die seit der letzten Abfrage aufgetreten sind. Die Anzahl der enthaltenen Ereignisse wird in motionEventsCount bzw. keyEventsCount gespeichert.

  1. Jedes Ereignis in der Spielschleife iterieren und verarbeiten In diesem Beispiel iteriert der folgende Code motionEvents und verarbeitet sie über 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);
    }
    

    Die Implementierung von _cookEventForPointerIndex() und weiterer zugehöriger Funktionen finden Sie im GitHub-Beispiel.

  2. Wenn Sie fertig sind, denken Sie daran, die Warteschlange der Ereignisse zu löschen, die Sie gerade behandelt haben:

    android_app_clear_motion_events(mApp);
    

Weitere Informationen

Weitere Informationen zu GameActivity finden Sie hier:

Wenn Sie Fehler melden oder neue Funktionen für GameActivity anfordern möchten, verwenden Sie die GameActivity-Problemverfolgung.