Pierwsze kroki z GameActivity Zawiera Android Game Development Kit.

Z tego przewodnika dowiesz się, jak skonfigurować usługę i zintegrować ją z kontem Google. GameActivity i obsługuj zdarzenia na Androidzie. tę grę.

GameActivity pomoże Ci przenieść język C lub z gry w C++ na Androida dzięki uproszczeniu procesu korzystania z kluczowych interfejsów API. Wcześniej NativeActivity był które są zalecane w przypadku gier. GameActivity zastępuje ją jako zalecaną dla gier i jest zgodna wstecznie z interfejsem API poziomu 19.

Przykład integrowania aktywności GameActivity znajdziesz w tabeli games-samples.

Zanim rozpoczniesz

Zobacz GameActivity wersji w uzyskać dystrybucję.

Konfigurowanie kompilacji

Na Androidzie Activity pełni funkcję wpisu za punkty, a także Window, aby rysować w środku. Wiele gier rozwija się Activity z własną klasą Java lub Kotlin, aby NativeActivity podczas używania kodu JNI do mostu kod gry w C lub C++.

GameActivity udostępnia te możliwości:

Aplikacja GameActivity jest rozpowszechniana jako archiwum Androida (AAR). Ten plik AAR zawiera klasę Java, która używane w AndroidManifest.xml, a także C i kod źródłowy C++, który łączy GameActivity stronę środowiska Java z Implementacja w języku C/C++. Jeśli używasz GameActivity w wersji 1.2.2 lub nowszej, kod C/C++ biblioteka statyczna. Jeśli jest to możliwe, zalecamy użycie ciągu biblioteki statycznej zamiast kodu źródłowego.

Uwzględnij te pliki źródłowe lub bibliotekę statyczną jako część procesu tworzenia kampanii Prefab który ujawnia biblioteki natywne i kod źródłowy Utwórz projekt lub kompilację NDK.

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

  2. Włącz prefab, wykonując te czynności: Wersja wtyczki Android (AGP) 4.1 lub nowsza:

    • Do bloku android w pliku build.gradle modułu dodaj:
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    Jeśli używasz starszych wersji AGP, postępuj zgodnie z dokumentację wstępną z użyciem odpowiednich instrukcji konfiguracji.

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

    Biblioteka statyczna

    W pliku CMakeLists.txt projektu zaimportuj statyczny element typu game-activity do modułu prefab 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

    Do pliku CMakeLists.txt projektu zaimportuj 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)
    

    Do folderu CmakeLists.txt projektu dołącz też te pliki: GameActivity.cpp, GameTextInput.cpp i android_native_app_glue.c.

Jak Android uruchamia Twoją aktywność

System Android wykonuje kod w wystąpieniu aktywności, wywołując wywołanie zwrotne. które odpowiadają konkretnym etapom cyklu życia aktywności. W zamówieniu na Androida, aby uruchomić aktywność i grę, musisz zadeklarować Twojej aktywności za pomocą odpowiednich atrybutów z pliku manifestu Androida. Więcej Więcej informacji zawiera Wprowadzenie do modułów

Plik manifestu Androida

Każdy projekt aplikacji musi mieć plik AndroidManifest.xml w lokalizacji elementu głównego zbioru źródłowego projektu. Plik manifestu opisuje informacje o aplikacji, narzędzia do kompilacji Androida, system operacyjny Android system i Google Play. Przykładowe zabezpieczenia:

Wdróż aktywność w grze w swojej grze

  1. Utwórz lub zidentyfikuj swoją główną klasę Java (określoną w tagu activity w pliku AndroidManifest.xml). Zmień te zajęcia na rozszerz GameActivity z pakietu com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Upewnij się, że biblioteka natywna została wczytana 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. Dodawanie biblioteki natywnej 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" />
    

Wdróż android_main

  1. Biblioteka android_native_app_glue to biblioteka kodu źródłowego, gra używa do zarządzania GameActivity zdarzeniami cyklu życia w osobnym wątku w aby zapobiec zablokowaniu treści w wątku głównym. Podczas korzystania z biblioteki rejestrujesz wywołanie zwrotne do obsługi zdarzeń cyklu życia, takich jak dotykowe wprowadzanie danych zdarzeń. Archiwum GameActivity zawiera własną wersję pliku android_native_app_glue, więc nie możesz korzystać z wersji zawartej w wersje NDK. Jeśli Twoje gry korzystają z biblioteki android_native_app_glue które jest zawarte w NDK, przejdź na wersję GameActivity.

    Po dodaniu kodu źródłowego biblioteki android_native_app_glue do projekt, łączy się on z zasadą GameActivity. Zaimplementować funkcję o nazwie android_main, które jest wywoływane przez i używać jej jako punktu wejścia w grę. Powodzenie 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. Przetwarzaj obiekt android_app w głównej pętli gry, np. w ankietach i obsłudze. zdarzeń cyklu aplikacji zdefiniowane w kombinacji NativeAppGlueAppCmd. Na przykład ten fragment kodu rejestruje funkcję _hand_cmd_proxy jako NativeAppGlueAppCmd, a następnie sonduje zdarzenia cyklu aplikacji i wysyła je do zarejestrowany moduł obsługi(w: android_app::onAppCmd) na potrzeby przetwarzania:

    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. Więcej informacji znajdziesz w opisie wdrożenia niekończącego się tunelu. Przykład NDK. Główna różnica będzie polegać na obsłudze zdarzeń przejdź do następnej sekcji.

Obsługa zdarzeń

Aby umożliwić dostęp do aplikacji zdarzenia wejściowe, utwórz i zarejestruj zdarzenie filtry z android_app_set_motion_event_filter i android_app_set_key_event_filter. Domyślnie biblioteka native_app_glue zezwala na zdarzenia ruchu tylko z SOURCE_TOUCHSCREEN dane wejściowe. Zapoznaj się z dokumentem referencyjnym i kod implmenetyki android_native_app_glue, by uzyskać szczegóły.

Aby obsługiwać zdarzenia wejściowe, uzyskaj odniesienie do elementu 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 ostatniego zdarzenia. Liczba zdarzeń jest przechowywana w motionEventsCount, keyEventsCount.

  1. Wykonuj iteracje i obsługuj każde zdarzenie w pętli gry. W tym przykładzie para klucz-wartość ten kod wykona iterację 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);
    }
    

    Zobacz Przykład z GitHub dla implementacji _cookEventForPointerIndex() i innych z powiązanych funkcji.

  2. Gdy skończysz, pamiętaj o wyczyszczeniu kolejki zdarzeń, które zostały niedawno obsługiwane:

    android_app_clear_motion_events(mApp);
    

Dodatkowe materiały

Więcej informacji o funkcji GameActivity:

Aby zgłosić błędy lub poprosić o nowe funkcje w GameActivity, skorzystaj z narzędzia do zgłaszania problemów w GameActivity.