Pierwsze kroki z GameActivity Część Android Game Development Kit.
Z tego przewodnika dowiesz się, jak skonfigurować i zintegrować GameActivity oraz obsługiwać zdarzenia w grze na Androida.
GameActivity ułatwia przenoszenie gier napisanych w C lub C++ na Androida, upraszczając proces korzystania z najważniejszych interfejsów API.
Wcześniej zalecaną klasą w przypadku gier była NativeActivity. GameActivity zastępuje go jako zalecana klasa w przypadku gier i jest wstecznie zgodny z poziomem API 19.
Przykładowy projekt integrujący GameActivity znajdziesz w repozytorium games-samples.
Zanim rozpoczniesz
Aby uzyskać dystrybucję, zapoznaj się z GameActivitywersjami.
Konfigurowanie kompilacji
Na Androidzie Activity jest punktem wejścia do gry i zapewnia też Window do rysowania. Wiele gier rozszerza tę Activity funkcję za pomocą własnej klasy Java lub Kotlin, aby pokonać ograniczenia w NativeActivity, używając kodu JNI do połączenia z kodem gry w języku C lub C++.
GameActivity oferuje te możliwości:
Dziedziczy po
AppCompatActivity, co umożliwia korzystanie z komponentów architektury Androida Jetpack.Renderuje element
SurfaceView, który umożliwia interakcję z dowolnym innym elementem interfejsu Androida.Obsługuje zdarzenia aktywności w języku Java. Umożliwia to zintegrowanie dowolnego elementu interfejsu Androida (np.
EditText,WebViewlubAd) z grą za pomocą interfejsu C.Oferuje interfejs API w języku C podobny do bibliotek
NativeActivityiandroid_native_app_glue.
GameActivity jest rozpowszechniany jako archiwum Androida (AAR). Ten plik AAR zawiera klasę Java, której używasz w AndroidManifest.xml, a także kod źródłowy w językach C i C++, który łączy część GameActivity w języku Java z implementacją aplikacji w językach C/C++. Jeśli używasz GameActivity w wersji 1.2.2 lub nowszej, dostępna jest też statyczna biblioteka C/C++. W odpowiednich przypadkach zalecamy używanie biblioteki statycznej zamiast kodu źródłowego.
Dołącz te pliki źródłowe lub bibliotekę statyczną do procesu kompilacji za pomocą Prefab, które udostępnia biblioteki natywne i kod źródłowy w projekcie CMake lub kompilacji NDK.
Aby dodać zależność biblioteki
GameActivitydo plikubuild.gradlegry, postępuj zgodnie z instrukcjami na stronie Jetpack Android Games.Aby włączyć prefab, wykonaj te czynności w przypadku wtyczki Androida w wersji 4.1 lub nowszej:
- Dodaj do bloku
androidw plikubuild.gradlemodułu te informacje:
buildFeatures { prefab true }- Wybierz wersję prefabrykowaną i ustaw ją w pliku
gradle.properties:
android.prefabVersion=2.0.0Jeśli używasz starszych wersji AGP, postępuj zgodnie z instrukcjami konfiguracji podanymi w dokumentacji prefabrykatu.
- Dodaj do bloku
Zaimportuj do projektu bibliotekę statyczną C/C++ lub kod źródłowy C/++ w ten sposób:
Biblioteka statyczna
W pliku
CMakeLists.txtprojektu zaimportuj bibliotekę statycznągame-activitydo modułu prefabrykatugame-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.txtprojektu zaimportuj pakietgame-activityi dodaj go do celu. Pakietgame-activitywymaga pakietulibandroid.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.txtprojektu dodaj też te pliki:GameActivity.cpp,GameTextInput.cppiandroid_native_app_glue.c.
Jak Android uruchamia Twoją aktywność
System Android wykonuje kod w instancji działania, wywołując metody zwrotne, które odpowiadają poszczególnym etapom cyklu życia działania. Aby Android mógł uruchomić aktywność i rozpocząć grę, musisz zadeklarować aktywność z odpowiednimi atrybutami w pliku manifestu Androida. Więcej informacji znajdziesz w artykule Wprowadzenie do działań.
Manifest Androida
Każdy projekt aplikacji musi zawierać plik AndroidManifest.xml w katalogu głównym zestawu źródeł projektu. Plik manifestu zawiera podstawowe informacje o aplikacji dla narzędzi do kompilacji Androida, systemu operacyjnego Android i Google Play. Obejmuje to m.in.:
Nazwa pakietu i identyfikator aplikacji umożliwiające jednoznaczną identyfikację gry w Google Play.
Komponenty aplikacji, takie jak działania, usługi, odbiorniki transmisji i dostawcy treści.
Uprawnienia dostępu do chronionych części systemu lub innych aplikacji.
Zgodność urządzenia określ wymagania sprzętowe i programowe dotyczące Twojej gry.
Nazwa biblioteki natywnej dla
GameActivityiNativeActivity(domyślnie libmain.so).
Implementowanie GameActivity w grze
Utwórz lub zidentyfikuj główną klasę Java aktywności (tę, która jest określona w elemencie
activityw plikuAndroidManifest.xml). Zmień tę klasę, aby rozszerzyćGameActivityz pakietucom.google.androidgamesdk:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }Upewnij się, że biblioteka natywna jest wczytywana na początku za pomocą 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"); } ... }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" />
Implementacja funkcji android_main
android_native_app_glueBiblioteka to biblioteka kodu źródłowego, której gra używa do zarządzaniaGameActivityzdarzeniami cyklu życia w osobnym wątku, aby zapobiec blokowaniu w wątku głównym. Gdy korzystasz z biblioteki, rejestrujesz wywołanie zwrotne do obsługi zdarzeń związanych z cyklem życia, takich jak zdarzenia wejścia dotykowego. ArchiwumGameActivityzawiera własną wersję bibliotekiandroid_native_app_glue, więc nie możesz używać wersji zawartej w wersjach NDK. Jeśli Twoje gry korzystają zandroid_native_app_gluebiblioteki dołączonej do NDK, przełącz się na wersjęGameActivity.Po dodaniu kodu źródłowego biblioteki
android_native_app_gluedo projektu będzie on współpracować zGameActivity. Zaimplementuj funkcję o nazwieandroid_main, która jest wywoływana przez bibliotekę i używana jako punkt wejścia do gry. Przekazywana jest do niej struktura o nazwieandroid_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; }Przetwarzaj
android_appw głównej pętli gry, np. przez odpytywanie i obsługę zdarzeń cyklu życia aplikacji zdefiniowanych w NativeAppGlueAppCmd. Na przykład ten fragment kodu rejestruje funkcję_hand_cmd_proxyjako moduł obsługiNativeAppGlueAppCmd, a następnie odpytuje o zdarzenia cyklu życia aplikacji i wysyła je do zarejestrowanego modułu obsługi(wandroid_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_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(); } } }Więcej informacji znajdziesz w przykładzie NDK Endless Tunnel. Główna różnica będzie polegać na sposobie obsługi zdarzeń, co pokazujemy w następnej sekcji.
Obsługa zdarzeń
Aby umożliwić zdarzeniom wejściowym docieranie do aplikacji, utwórz i zarejestruj filtry zdarzeń za pomocą funkcji android_app_set_motion_event_filter i android_app_set_key_event_filter.
Domyślnie biblioteka native_app_glue zezwala tylko na zdarzenia ruchu pochodzące z wejścia SOURCE_TOUCHSCREEN. Szczegółowe informacje znajdziesz w dokumencie referencyjnym i w android_native_app_gluekodzie implementacji.
Aby obsługiwać zdarzenia wejściowe, uzyskaj odniesienie do android_input_buffer za pomocą
android_app_swap_input_buffers()
w pętli gry. Zawierają one zdarzenia związane z ruchem i kluczowe zdarzenia, które miały miejsce od ostatniego odpytania. Liczba zdarzeń zawartych w odpowiednich polach jest przechowywana w polach motionEventsCount i keyEventsCount.
Iteruj i obsługuj każde zdarzenie w pętli gry. W tym przykładzie poniższy kod iteruje
motionEventsi 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); }Przykład implementacji funkcji
_cookEventForPointerIndex()i innych powiązanych funkcji znajdziesz w przykładzie w GitHubie.Po zakończeniu pamiętaj, aby wyczyścić kolejkę zdarzeń, które zostały właśnie obsłużone:
android_app_clear_motion_events(mApp);
Dodatkowe materiały
Więcej informacji o GameActivity znajdziesz w tych artykułach:
- Informacje o wersjach GameActivity i AGDK
- Użyj GameTextInput w GameActivity
- Przewodnik po migracji NativeActivity
- Dokumentacja referencyjna GameActivity
- Implementacja GameActivity
Aby zgłosić błędy lub poprosić o dodanie nowych funkcji do GameActivity, użyj narzędzia do śledzenia problemów z GameActivity.