Erste Schritte mit GameActivity Teil des Android Game Development Kit.

In diesem Leitfaden wird die Einrichtung und Integration GameActivity und Ereignisse auf Ihrem Android-Gerät verwalten Spiel.

Mit GameActivity können Sie Ihre C- oder C++-Spiele in Android durch die Vereinfachung der Verwendung wichtiger APIs Bisher wurde NativeActivity empfohlenen Kurs für Spiele. GameActivity ersetzt es als empfohlene und ist abwärtskompatibel mit API-Level 19.

Ein Beispiel mit GameActivity-Einbindung finden Sie in der Repository für Spielebeispiele.

Vorbereitung

Siehe GameActivity-Releases für um eine Verteilung zu erhalten.

Build einrichten

Unter Android dient ein Activity als Eintrag für Ihr Spiel und bietet Ihnen Window, um darin zu zeichnen. Viele Spiele erweitern Activity mit ihrer eigenen Java- oder Kotlin-Klasse, um Einschränkungen in NativeActivity, während mit JNI-Code eine Bridge verwendet wird in ihren C- oder C++-Spielcode ein.

GameActivity bietet folgende Funktionen:

GameActivity wird als Android Archive vertrieben. automatisch angewendete Empfehlungen. Dieses AAR enthält die Java-Klasse, die Sie in Ihrem AndroidManifest.xml sowie das C und C++ Quellcode, der die Java-Seite von GameActivity mit der Implementierung in C/C++. Wenn Sie GameActivity 1.2.2 oder höher verwenden, wird die C/C++- statische Bibliothek zur Verfügung. Wir empfehlen, wenn möglich, die statische Bibliothek anstelle des Quellcodes verwenden.

Fügen Sie diese Quelldateien oder die statische Bibliothek als Teil Ihres während des gesamten Prozesses Prefab, der native Bibliotheken und Quellcode für Ihre CMake-Projekt oder NDK-Build

  1. Folgen Sie der Anleitung auf der Jetpack Android Games (Jetpack-Android-Spiele) GameActivity-Bibliotheksabhängigkeit zur build.gradle-Datei deines Spiels.

  2. Aktivieren Sie Prefab, indem Sie Folgendes mit Android-Plug-in-Version (AGP) ab 4.1:

    • Fügen Sie Folgendes in den Block android der Datei build.gradle Ihres Moduls ein:
    buildFeatures {
        prefab true
    }
    
    • Wählen Sie eine Vorabversion aus. und legen Sie dafür die Datei gradle.properties fest:
    android.prefabVersion=2.0.0
    

    Wenn Sie frühere AGP-Versionen verwenden, Prefab-Dokumentation finden Sie die entsprechende Konfigurationsanleitung.

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

    Static-Bibliothek

    Importieren Sie das statische game-activity-Objekt in die Datei CMakeLists.txt Ihres Projekts. in das game-activity_static-Fertigmodul:

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

    Quellcode

    Importieren Sie den game-activity in die Datei CMakeLists.txt Ihres Projekts. Paket und fügen es Ihrem Ziel hinzu. Das game-activity-Paket benötigt libandroid.so. Fehlt sie, müssen Sie sie auch 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 CmakeLists.txt Ihres Projekts ein: GameActivity.cpp, GameTextInput.cpp und android_native_app_glue.c.

So startet Android Ihre Aktivitäten

Das Android-System führt durch Aufrufen eines Callbacks Code in Ihrer Activity-Instanz aus die bestimmten Phasen des Aktivitätslebenszyklus entsprechen. Um Damit Android deine Aktivität starten und dein Spiel starten kann, musst du mit den entsprechenden Attributen im Android-Manifest. Weitere Informationen finden Sie unter Einführung in Aktivitäten.

Android-Manifest

Jedes App-Projekt muss einen AndroidManifest.xml-Datei unter Stamm des Quellsatzes des Projekts. In der Manifestdatei werden wichtige Informationen zu Ihrer App an die Android-Build-Tools, das Android-Betriebssystem und Google Play. Sie beinhalten die folgenden Funktionen:

GameActivity im Spiel implementieren

  1. Erstellen oder identifizieren Sie Ihre Java-Klasse für die Hauptaktivität (die im activity-Element in der Datei AndroidManifest.xml). Diesen Kurs ändern in Erweitern Sie GameActivity aus dem com.google.androidgamesdk-Paket:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Sorgen Sie dafür, dass Ihre native Bibliothek von vornherein 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. Native Bibliothek zu AndroidManifest.xml hinzufügen Wenn der Name der Bibliothek nicht der Standardname (libmain.so) ist:

    <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, die von Ihrem in dem Spiel GameActivity Lifecycle-Events in einem separaten Thread in um eine Blockierung in Ihrem Hauptthread zu verhindern. Wenn Sie die Bibliothek verwenden, registrierst du den Callback für die Verarbeitung von Lebenszyklus-Ereignissen wie Berührungseingaben. Ereignisse. Das GameActivity-Archiv enthält eine eigene Version des android_native_app_glue-Bibliothek, daher können Sie die Version aus NDK-Releases Wenn deine Spiele die android_native_app_glue-Mediathek verwenden die im NDK enthalten ist, wechseln Sie zur GameActivity-Version.

    Nachdem Sie den Quellcode der android_native_app_glue-Bibliothek zu Ihrem verknüpft ist, besteht die Verbindung zu GameActivity. Implementieren Sie eine Funktion namens android_main, das vom und als Einstiegspunkt für dein Spiel verwendet. Es wird ein Struktur namens android_app. Das kann je nach Spiel und Engine unterschiedlich sein. 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. android_app in deiner Hauptspielschleife verarbeiten, z. B. Abfragen und Verarbeitung in NativeAppGlueAppCmd definierte App-Zyklusereignisse. Mit dem folgenden Snippet wird beispielsweise die Funktion _hand_cmd_proxy als NativeAppGlueAppCmd-Handler, fragt dann App-Zyklusereignisse ab und sendet sie an den registrierter Handler(in android_app::onAppCmd) für die Verarbeitung:

    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. Untersuchen Sie für weitere Informationen die Implementierung des Endless Tunnels. NDK-Beispiel. Der Hauptunterschied besteht darin, wie Ereignisse verarbeitet werden, wie in im nächsten Abschnitt.

Ereignisse verarbeiten

Erstelle und registriere dein Ereignis, damit Eingabeereignisse deine App erreichen können Filter mit android_app_set_motion_event_filter und android_app_set_key_event_filter. Standardmäßig erlaubt die native_app_glue-Mediathek nur Bewegungsereignisse von QUELL-TOUCHSCREEN Eingabe. Sieh dir auch die Referenzdokumentation an. und den Implementierungscode android_native_app_glue für die Details.

Um Eingabeereignisse zu verarbeiten, rufen Sie einen Verweis auf android_input_buffer ab mit android_app_swap_input_buffers() in Ihrer Spielschleife. Dazu gehören Bewegungsereignisse und Schlüsselereignisse, die seit der letzten Ausführung dieser Aktion aufgetreten sind. . Die Anzahl der enthaltenen Ereignisse wird in motionEventsCount gespeichert. keyEventsCount.

  1. Iteriere und bearbeite jedes Ereignis in deiner Spielschleife. In diesem Beispiel hat der Parameter Der folgende Code iteriert 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);
    }
    

    Weitere Informationen finden Sie in der GitHub-Beispiel zur Implementierung von _cookEventForPointerIndex() und anderen verwandten Funktionen.

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

    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 den GameActivity-Issue Tracker.