Pierwsze kroki z GameActivity Część pakietu Android Game Development Kit.

Z tego przewodnika dowiesz się, jak skonfigurować i integrować GameActivity oraz obsługiwać wydarzenia w grze na Androida.

GameActivity ułatwia przenoszenie gier w języku C lub C++ na Androida przez uproszczenie korzystania z krytycznych interfejsów API. Wcześniej NativeActivity była zalecaną klasą dla gier. GameActivity zastępuje ją jako zalecaną klasę dla gier i jest zgodna wstecznie z poziomem interfejsu API 19.

Przykład integrujący GameActivity znajdziesz w repozytorium giers-samples.

Zanim zaczniesz

Aby uzyskać dystrybucję, zobacz GameActivity wersje.

Konfigurowanie kompilacji

Na Androidzie Activity pełni funkcję punktu wejścia do gry i Window, w którym możesz rysować. Wiele gier rozszerza ten Activity o własną klasę Javy lub Kotlin, aby pokonać ograniczenia w NativeActivity, a jednocześnie używa kodu JNI do łączenia się z kodem gry w C lub C++.

GameActivity oferuje te możliwości:

Aplikacja GameActivity jest rozpowszechniana jako archiwum Androida (AAR). To AAR zawiera klasę Javy, której używasz w AndroidManifest.xml, a także kod źródłowy C i C++, który łączy stronę GameActivity w Javie z implementacją C/C++ aplikacji. Jeśli używasz GameActivity w wersji 1.2.2 lub nowszej, dostępna jest też biblioteka statyczna C/C++. W stosownych przypadkach zalecamy używanie biblioteki statycznej zamiast kodu źródłowego.

Uwzględnij te pliki źródłowe lub bibliotekę statyczną w ramach procesu kompilacji w Prefab, co spowoduje udostępnienie bibliotek natywnych i kodu źródłowego w projekcie CMake lub kompilacji NDK.

  1. Postępuj zgodnie z instrukcjami na stronie Jetpack na Androida Games, aby dodać zależność biblioteki GameActivity do pliku build.gradle gry.

  2. Włącz prefab, wykonując te czynności w wersji wtyczki do Androida (AGP) 4.1 lub nowszej:

    • Dodaj ten fragment do bloku android w pliku build.gradle modułu:
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    Jeśli używasz wcześniejszych wersji AGP, postępuj zgodnie z odpowiednimi instrukcjami konfiguracji zgodnie z dokumentacją prefabową.

  3. Zaimportuj do projektu bibliotekę statyczną C/C++ lub kod źródłowy C/++ w następujący sposób.

    Biblioteka statyczna

    W pliku CMakeLists.txt projektu zaimportuj bibliotekę statyczną game-activity do modułu prefabowego game-activity_static:

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

    Kod źródłowy

    W pliku CMakeLists.txt projektu zaimportuj pakiet game-activity i dodaj go do miejsca docelowego. Pakiet game-activity wymaga libandroid.so, więc jeśli go brakuje, musisz go też zaimportować.

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

    Umieść też w folderze CmakeLists.txt projektu te pliki: GameActivity.cpp, GameTextInput.cpp i android_native_app_glue.c.

Jak Android uruchamia Twoją Aktywność

System Android uruchamia kod w instancji Aktywność, wywołując metody wywołania zwrotnego, które odpowiadają określonym etapom cyklu życia aktywności. Aby Android mógł uruchomić aktywność i uruchomić grę, musisz zadeklarować swoją aktywność za pomocą odpowiednich atrybutów w pliku manifestu Androida. Więcej informacji znajdziesz w artykule Wprowadzenie do działań.

Plik manifestu Androida

Każdy projekt aplikacji musi mieć w katalogu głównym zbioru źródłowego projektu plik AndroidManifest.xml. Plik manifestu zawiera podstawowe informacje o aplikacji, które są dostępne w narzędziach do tworzenia aplikacji na Androida, systemie operacyjnym Android i w Google Play. Przykładowe działania:

Wdróż aktywność o grach w swojej grze

  1. Utwórz lub wskaż swoją główną klasę Javy aktywności (klasę określoną w elemencie activity w pliku AndroidManifest.xml). Zmień tę klasę, aby rozszerzyć GameActivity z pakietu com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Biblioteka natywna powinna być wczytywana na początku przy użyciu bloku statycznego:

    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. Dodaj bibliotekę natywną do AndroidManifest.xml, jeśli nazwa biblioteki nie jest nazwą domyślną (libmain.so):

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

Implementowanie elementu android_main

  1. Biblioteka android_native_app_glue to biblioteka kodu źródłowego, której gra używa do zarządzania zdarzeniami cyklu życia GameActivity w osobnym wątku, aby zapobiec blokowaniu w wątku głównym. Gdy korzystasz z biblioteki, rejestrujesz wywołanie zwrotne do obsługi zdarzeń cyklu życia, takich jak zdarzenia dotykowego wprowadzania danych. Archiwum GameActivity zawiera własną wersję biblioteki android_native_app_glue, więc nie możesz korzystać z wersji zawartej w wersjach NDK. Jeśli Twoje gry używają biblioteki android_native_app_glue zawartej w pakiecie NDK, przejdź na wersję GameActivity.

    Gdy dodasz do projektu kod źródłowy biblioteki android_native_app_glue, będzie on obsługiwać interfejs GameActivity. Zaimplementuj funkcję o nazwie android_main, która jest wywoływana przez bibliotekę i używana jako punkt wejścia do gry. Jest przekazywany do struktury o nazwie android_app. Może się to różnić w zależności od gry i silnika. Oto przykład:

    #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. Przetwórz android_app w głównej pętli gry, np. odpytywanie i obsługę zdarzeń cyklu aplikacji zdefiniowanych w elemencie NativeAppGlueAppCmd. Na przykład ten fragment kodu rejestruje funkcję _hand_cmd_proxy jako moduł obsługi NativeAppGlueAppCmd, a następnie odpytuje zdarzenia cyklu aplikacji i wysyła je do zarejestrowanego modułu obsługi(w android_app::onAppCmd) w celu przetworzenia:

    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. Aby dowiedzieć się więcej, poczytaj o implementacji tunelu bez końca NDK. Główna różnica będzie polegać na sposobie obsługi zdarzeń, jak pokazano w następnej sekcji.

Obsługa zdarzeń

Aby umożliwić dostęp do aplikacji zdarzeń wejściowych, utwórz i zarejestruj filtry zdarzeń za pomocą android_app_set_motion_event_filter i android_app_set_key_event_filter. Domyślnie biblioteka native_app_glue zezwala tylko na zdarzenia ruchu z danych wejściowych SOURCE_TOUCHSCREEN. Szczegóły znajdziesz w dokumentacji referencyjnej i kodzie implemencji android_native_app_glue.

Aby obsługiwać zdarzenia wejściowe, pobierz odniesienie do obiektu android_input_buffer z parametrem android_app_swap_input_buffers() w pętli gry. Obejmują one zdarzenia ruchu i kluczowe zdarzenia, które miały miejsce od czasu ostatniego sprawdzenia. Liczba zawartych zdarzeń jest przechowywana odpowiednio w motionEventsCount i keyEventsCount.

  1. Powtarzaj i obsługuj każde zdarzenie w pętli gry. W tym przykładzie poniższy kod iteruje motionEvents i obsługuje je za pomocą 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);
    }
    

    Informacje o implementacji funkcji _cookEventForPointerIndex() i innych powiązanych funkcji znajdziesz w przykładzie na GitHubie.

  2. Gdy skończysz, pamiętaj o wyczyszczeniu kolejki obsługiwanych właśnie zdarzeń:

    android_app_clear_motion_events(mApp);
    

Dodatkowe materiały

Więcej informacji o GameActivity znajdziesz tutaj:

Jeśli chcesz zgłosić błędy lub poprosić o nowe funkcje w GameActivity, skorzystaj z narzędzia do śledzenia problemów z GameActivity.