Cómo comenzar a usar GameActivity Parte de Android Game Development Kit.
En esta guía, se describe cómo integrar y configurar GameActivity
, y controlar eventos en tu juego para Android.
GameActivity
te ayuda a llevar tu juego de C o C++ a Android simplificando el proceso de uso de APIs críticas.
Anteriormente, NativeActivity
era la clase recomendada para los juegos. GameActivity
la reemplaza y es retrocompatible con el nivel de API 19.
Para ver una muestra de que está integrada en GameActivity, consulta el repositorio de muestras de juegos.
Antes de comenzar
Consulta las versiones de GameActivity
para obtener una distribución.
Cómo configurar tu compilación
En Android, una Activity
sirve como punto de entrada para tu juego y también proporciona la Window
en la que dibujar. Muchos juegos extienden esta Activity
con su propia clase de Java o Kotlin para superar las limitaciones de NativeActivity
y usan código JNI
de modo que se conecte al código C o C++ de su juego.
GameActivity
ofrece las siguientes funciones:
Se hereda de
AppCompatActivity
, lo que te permite usar componentes de la arquitectura de Android Jetpack.Se renderiza en una
SurfaceView
que te permite interactuar con cualquier otro elemento de la IU de Android.Controla los eventos de actividad de Java. Esto permite que cualquier elemento de la IU de Android (como un
EditText
, unaWebView
o unAd
) se integre a tu juego mediante una interfaz C.Ofrece una API para C similar a
NativeActivity
y a la bibliotecaandroid_native_app_glue
.
GameActivity
se distribuye como un Android ARchive (AAR). Este AAR contiene la clase Java que usas en tu AndroidManifest.xml
, así como el código fuente C y C++ que conecta el lado Java de GameActivity
a la implementación de C/C++ de la app. Si usas GameActivity
1.2.2 o una versión posterior, también se proporciona la biblioteca estática C/C++. Siempre que sea aplicable, te recomendamos que uses la biblioteca estática en lugar del código fuente.
Incluye estos archivos de origen o la biblioteca estática como parte del proceso de compilación mediante Prefab
, que expone las bibliotecas nativas y el código fuente a tu Proyecto de CMake o compilación del NDK.
Sigue las instrucciones en la página de juegos de Android para Jetpack para agregar la dependencia de la biblioteca
GameActivity
al archivobuild.gradle
de tu juego.Si quieres habilitar prefab, haz lo siguiente con la versión 4.1 o posteriores del complemento para Android (AGP):
- Agrega lo siguiente al bloque
android
del archivobuild.gradle
de tu módulo:
buildFeatures { prefab true }
- Elige una versión de Prefab y configúrala con el archivo
gradle.properties
:
android.prefabVersion=2.0.0
Si usas versiones anteriores del AGP, sigue la documentación de Prefab para obtener las instrucciones de configuración correspondientes.
- Agrega lo siguiente al bloque
Importa la biblioteca estática de C/C++ o el código fuente de C/++ a tu proyecto de la siguiente manera.
Biblioteca estática
En el archivo
CMakeLists.txt
de tu proyecto, importa la biblioteca estáticagame-activity
al módulo prefabgame-activity_static
:find_package(game-activity REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PUBLIC log android game-activity::game-activity_static)
Código fuente
En el archivo
CMakeLists.txt
de tu proyecto, importa el paquetegame-activity
y agrégalo a tu destino: El paquetegame-activity
requierelibandroid.so
, por lo que, si falta, también debes importarlo.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
Además, incluye los siguientes archivos en el
CmakeLists.txt
de tu proyecto:GameActivity.cpp
,GameTextInput.cpp
yandroid_native_app_glue.c
.
Cómo Android inicia tu actividad
El sistema Android ejecuta el código en tu instancia de Activity invocando métodos de devolución de llamada que corresponden a etapas específicas del ciclo de vida de la actividad. Para que Android inicie la actividad y tu juego, debes declarar la actividad con los atributos adecuados en el Manifiesto de Android. Si deseas obtener más información, consulta Introducción a las actividades.
Manifiesto de Android
Cada proyecto de app debe tener un archivo AndroidManifest.xml en la raíz del conjunto de orígenes del proyecto. El archivo de manifiesto describe información esencial sobre tu app para las herramientas de compilación de Android, el sistema operativo Android y Google Play. Esto incluye lo siguiente:
El nombre del paquete y el ID de la app para identificar tu juego en Google Play de forma única
Componentes de la app, como actividades, servicios, receptores de transmisiones y proveedores de contenido
Permisos para acceder a las partes protegidas del sistema o a otras apps
Compatibilidad con dispositivos para especificar los requisitos de hardware y software de tu juego.
Es el nombre de la biblioteca nativa de
GameActivity
yNativeActivity
(la configuración predeterminada es libmain.so).
Cómo implementar GameActivity en tu juego
Crea o identifica la clase de Java de tu actividad principal (la especificada en el elemento
activity
dentro de tu archivoAndroidManifest.xml
). Cambia esta clase para extenderGameActivity
desde el paquetecom.google.androidgamesdk
:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
Asegúrate de que tu biblioteca nativa se cargue al comienzo con un bloque estático:
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"); } ... }
Agrega tu biblioteca nativa a
AndroidManifest.xml
si el nombre de tu biblioteca no es el predeterminado (libmain.so
):<meta-data android:name="android.app.lib_name" android:value="android-game" />
Cómo implementar android_main
La biblioteca de
android_native_app_glue
es una biblioteca de código fuente que tu juego usa para administrar eventos de ciclo de vida deGameActivity
en un subproceso independiente y así evitar bloqueos en tu subproceso principal. Cuando uses la biblioteca, registras la devolución de llamada para controlar eventos de ciclo de vida, como la entrada táctil eventos. El archivoGameActivity
incluye su propia versión de la bibliotecaandroid_native_app_glue
, por lo que no puedes usar la versión incluida en las versiones del NDK. Si tus juegos usan la bibliotecaandroid_native_app_glue
que se incluye en el NDK, cambia a la versiónGameActivity
.Después de agregar el código fuente de la biblioteca
android_native_app_glue
a tu proyecto, este interactuará conGameActivity
. Implementa una función llamadaandroid_main
, que la biblioteca llama y que se usa como punto de entrada para tu juego. Se le pasa una estructura llamadaandroid_app
. Esto puede diferir según el juego y el motor. Por ejemplo:#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; }
Procesa
android_app
en el bucle principal del juego. Puedes consultar y administrar eventos de ciclo de la app definidos en NativeAppGlueAppCmd. Por ejemplo, en el siguiente fragmento, se registra la función_hand_cmd_proxy
como el controladorNativeAppGlueAppCmd
; luego, sondea los eventos de ciclo de la app y los envía al controlador registrado (enandroid_app::onAppCmd
) para su procesamiento: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(); } } }
Si deseas obtener más información, estudia la implementación de Endless Tunnel Ejemplo de NDK. La diferencia principal estará dada por la forma de controlar los eventos, como se muestra en la siguiente sección.
Cómo controlar eventos
Para permitir que los eventos de entrada lleguen a tu app, crea y registra los filtros de evento con android_app_set_motion_event_filter
y android_app_set_key_event_filter
.
De forma predeterminada, la biblioteca native_app_glue
solo permite eventos de movimiento desde la entrada SOURCE_TOUCHSCREEN. Asegúrate de consultar el documento de referencia y el código de implementación android_native_app_glue
para obtener los detalles.
Para controlar eventos de entrada, obtén una referencia al android_input_buffer
con android_app_swap_input_buffers()
en el bucle de juego. Estos contienen eventos de movimiento y eventos clave que ocurrieron desde la última vez que se sondeó. La cantidad de eventos contenidos se almacena en motionEventsCount
y keyEventsCount
respectivamente.
Itera y controla cada evento en tu bucle de juego. En este ejemplo, el siguiente código itera
motionEvents
y los administra mediantehandle_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); }
Consulta la Muestra de GitHub para la implementación de
_cookEventForPointerIndex()
y otras funciones relacionadas.Cuando termines, recuerda borrar la cola de eventos que acabas de controlar:
android_app_clear_motion_events(mApp);
Recursos adicionales
Para obtener más información sobre GameActivity
, consulta los siguientes vínculos:
- Notas de la versión de AGDK y GameActivity
- Usa GameTextInput en GameActivity
- Guía de migración de NativeActivity
- Documentación de referencia de GameActivity
- Implementación de GameActivity
Para informar errores o solicitar nuevas funciones a GameActivity, usa la herramienta de seguimiento de errores de GameActivity.