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 GameActivity
wersjami.
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
,WebView
lubAd
) z grą za pomocą interfejsu C.Oferuje interfejs API w języku C podobny do bibliotek
NativeActivity
iandroid_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
GameActivity
do plikubuild.gradle
gry, 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
android
w plikubuild.gradle
modułu te informacje:
buildFeatures { prefab true }
- Wybierz wersję prefabrykowaną i ustaw ją w pliku
gradle.properties
:
android.prefabVersion=2.0.0
Jeś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.txt
projektu zaimportuj bibliotekę statycznągame-activity
do 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.txt
projektu zaimportuj pakietgame-activity
i dodaj go do celu. Pakietgame-activity
wymaga 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.txt
projektu dodaj też te pliki:GameActivity.cpp
,GameTextInput.cpp
iandroid_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
GameActivity
iNativeActivity
(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
activity
w plikuAndroidManifest.xml
). Zmień tę klasę, aby rozszerzyćGameActivity
z 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_glue
Biblioteka to biblioteka kodu źródłowego, której gra używa do zarządzaniaGameActivity
zdarzeniami 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. ArchiwumGameActivity
zawiera 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_glue
biblioteki dołączonej do NDK, przełącz się na wersjęGameActivity
.Po dodaniu kodu źródłowego biblioteki
android_native_app_glue
do 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_app
w 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_proxy
jako 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_glue
kodzie 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
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); }
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.