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:
Dziedziczy wartość z tabeli
AppCompatActivity
, co pozwala na używanie komponentów architektury Jetpack w Androidzie.Wyświetla się w elemencie
SurfaceView
, który umożliwia interakcję z dowolnymi innymi elementami interfejsu Androida.Obsługuje zdarzenia aktywności Javy. Dzięki temu dowolny element interfejsu Androida (np.
EditText
,WebView
czyAd
) może zostać zintegrowany z grą za pomocą interfejsu C.Udostępnia interfejs C API podobny do
NativeActivity
oraz bibliotekęandroid_native_app_glue
.
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.
Postępuj zgodnie z instrukcjami na stronie Jetpack na Androida Games, aby dodać zależność biblioteki
GameActivity
do plikubuild.gradle
gry.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 plikubuild.gradle
modułu:
buildFeatures { prefab true }
- Wybierz wersję Prefab i ustaw w niej plik
gradle.properties
:
android.prefabVersion=2.0.0
Jeśli używasz wcześniejszych wersji AGP, postępuj zgodnie z odpowiednimi instrukcjami konfiguracji zgodnie z dokumentacją prefabową.
- Dodaj ten fragment do bloku
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 prefabowegogame-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 miejsca docelowego. Pakietgame-activity
wymagalibandroid.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
iandroid_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. Obejmuje to te sytuacje:
Nazwa pakietu i identyfikator aplikacji, które jednoznacznie identyfikują Twoją grę w Google Play.
Komponenty aplikacji, takie jak aktywność, usługi, odbiorniki i dostawcy treści.
Uprawnienia umożliwiające dostęp do chronionych części systemu lub innych aplikacji.
Zgodność urządzeń, aby określić wymagania gry i sprzętu.
Nazwa biblioteki natywnej dla
GameActivity
iNativeActivity
(domyślnie libmain.so).
Wdróż aktywność o grach w swojej grze
Utwórz lub wskaż swoją główną klasę Javy aktywności (klasę określoną 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 { ... }
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"); } ... }
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
Biblioteka
android_native_app_glue
to biblioteka kodu źródłowego, której gra używa do zarządzania zdarzeniami cyklu życiaGameActivity
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. ArchiwumGameActivity
zawiera własną wersję bibliotekiandroid_native_app_glue
, więc nie możesz korzystać z wersji zawartej w wersjach NDK. Jeśli Twoje gry używają bibliotekiandroid_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ć interfejsGameActivity
. Zaimplementuj funkcję o nazwieandroid_main
, która jest wywoływana przez bibliotekę i używana jako punkt wejścia do gry. Jest przekazywany do struktury 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; }
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ługiNativeAppGlueAppCmd
, a następnie odpytuje zdarzenia cyklu 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_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(); } } }
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
.
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.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:
- Informacje o wersjach GameActivity i AGDK.
- Używaj elementu GameTextInput w ramach GameActivity.
- Przewodnik po migracji NativeActivity
- Dokumentacja referencyjna GameActivity.
- Implementacja GameActivity.
Jeśli chcesz zgłosić błędy lub poprosić o nowe funkcje w GameActivity, skorzystaj z narzędzia do śledzenia problemów z GameActivity.