Wprowadzenie do GameActivity części 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 w C lub C++ na Androida, upraszczając proces korzystania z najważniejszych interfejsów API.
Wcześniej NativeActivity była
zalecaną klasą dla gier. GameActivity zastępuje ją jako zalecaną klasę dla gier i jest wstecznie zgodna z poziomem API 19.
Przykład integracji GameActivity znajdziesz w repozytorium games-samples.
Zanim rozpoczniesz
Aby uzyskać dystrybucję, zapoznaj się z informacjami o wersjach GameActivityreleases to
obtain a distribution.
Konfigurowanie kompilacji
Na Androidzie Activity służy jako punkt wejścia do gry, a także udostępnia Window, w którym można rysować. Wiele gier rozszerza
tę Activity o własną klasę Java lub Kotlin, aby pokonać ograniczenia w
NativeActivity podczas używania JNI kodu do łączenia
się z kodem gry w C lub C++.
GameActivity oferuje te możliwości:
Dziedziczy z
AppCompatActivity, co pozwala na korzystanie z komponentów architektury Android Jetpack.Renderuje do
SurfaceView, co umożliwia interakcję z dowolnym innym elementem interfejsu Androida.Obsługuje zdarzenia aktywności Java. Dzięki temu dowolny element interfejsu Androida (np.
EditText,WebViewlubAd) można zintegrować z grą za pomocą interfejsu C.Oferuje interfejs C API podobny do
NativeActivityiandroid_native_app_gluebiblioteki.
GameActivity jest dystrybuowana jako archiwum Androida
(AAR). To archiwum AAR zawiera klasę Java, której
używasz w
AndroidManifest.xml, a także kod źródłowy w C
i C++, który łączy stronę Java GameActivity 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++. Jeśli to możliwe, 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óry 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.Włącz prefab, wykonując te czynności w przypadku wtyczki Androida w wersji 4.1 lub nowszej:
- Dodaj ten kod do bloku
androidw plikubuild.gradlemodułu:
buildFeatures { prefab true }- Wybierz wersję prefab,
i ustaw ją w pliku
gradle.properties:
android.prefabVersion=2.0.0- Dodaj ten kod do bloku
Zaimportuj do projektu bibliotekę statyczną C/C++ lub kod źródłowy C/C++ w ten sposób.
Biblioteka statyczna
W pliku
CMakeLists.txtprojektu zaimportuj bibliotekę statycznągame-activitydo modułu prefabgame-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-activitywymagalibandroid.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 pliku
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 Activity, wywołując metody wywołania zwrotnego odpowiadające określonym etapom cyklu życia działania. Aby Android mógł uruchomić Twoją aktywność i rozpocząć grę, musisz zadeklarować aktywność z odpowiednimi atrybutami w manifeście Androida. Więcej informacji znajdziesz w artykule Wprowadzenie do aktywności.
Manifest Androida
Każdy projekt aplikacji musi mieć plik AndroidManifest.xml w katalogu głównym zbioru ź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.:
Nazwę pakietu i identyfikator aplikacji które jednoznacznie identyfikują grę w Google Play.
Komponenty aplikacji, takie jak aktywności, usługi, odbiorniki transmisji i dostawcy treści.
Uprawnienia dostępu do chronionych części systemu lub innych aplikacji.
Zgodność z urządzeniem aby określić wymagania sprzętowe i programowe dotyczące gry.
Nazwę 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 rozszerzałaGameActivityz pakietucom.google.androidgamesdk:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }Upewnij się, że biblioteka natywna jest ładowana 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"); } ... }Jeśli nazwa biblioteki nie jest nazwą domyślną (
libmain.so), dodaj bibliotekę natywną doAndroidManifest.xml:<meta-data android:name="android.app.lib_name" android:value="android-game" />
Implementowanie android_main
Biblioteka
android_native_app_glueto biblioteka kodu źródłowego, której gra używa do zarządzania zdarzeniami cyklu życiaGameActivityw osobnym wątku, aby zapobiec blokowaniu w wątku głównym. Gdy używasz biblioteki, rejestrujesz wywołanie zwrotne do obsługi zdarzeń cyklu życia, takich jak zdarzenia wprowadzania dotykowego. ArchiwumGameActivityzawiera własną wersję bibliotekiandroid_native_app_glue, więc nie możesz używać wersji dołączonej do wersji NDK. Jeśli Twoje gry używają bibliotekiandroid_native_app_gluedołączonej do NDK, przejdź na wersjęGameActivity.Po dodaniu kodu źródłowego biblioteki
android_native_app_gluedo projektu będzie ona 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 a 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 sondowanie i obsługę zdarzeń cyklu aplikacji zdefiniowanych w NativeAppGlueAppCmd. Na przykład ten fragment kodu rejestruje funkcję_hand_cmd_proxyjako obsługęNativeAppGlueAppCmd, a następnie sondowanie zdarzeń cyklu aplikacji i wysyła je do zarejestrowanej 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 implementacji przykładu Endless Tunnel 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ć docieranie zdarzeń wprowadzania danych do aplikacji, 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ółowe informacje znajdziesz w dokumentacji
i kodzie implementacji android_native_app_glue.
Aby obsługiwać zdarzenia wprowadzania danych, uzyskaj odniesienie do android_input_buffer za pomocą
android_app_swap_input_buffers()
w pętli gry. Zawierają one zdarzenia ruchu i zdarzenia klawiszy, które miały miejsce od ostatniego
sondowania. Liczba zawartych zdarzeń jest przechowywana odpowiednio w motionEventsCount i keyEventsCount.
Iteruj i obsługuj każde zdarzenie w pętli gry. W tym przykładzie ten 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); }Implementację
_cookEventForPointerIndex()i innych powiązanych funkcji znajdziesz w przykładzie w GitHubie.Gdy skończysz, pamiętaj, aby wyczyścić kolejkę zdarzeń, które właśnie zostały obsłużone:
android_app_clear_motion_events(mApp);
Dodatkowe materiały
Więcej informacji o GameActivity znajdziesz w tych materiałach:
- Informacje o wersjach GameActivity i AGDK.
- Korzystanie z GameTextInput w GameActivity.
- Przewodnik po migracji NativeActivity.
- Dokumentacja na temat GameActivity
- Implementacja GameActivity.
Aby zgłosić błąd lub poprosić o dodanie nowych funkcji w GameActivity, użyj problem trackera GameActivity.