Começar a usar a GameActivity Parte do Android Game Development Kit.
Este guia descreve como configurar e integrar o
GameActivity
e processar eventos no seu jogo
Android.
A GameActivity
ajuda a levar seu jogo em C ou
C++ para o Android simplificando o processo de uso de APIs essenciais.
Anteriormente, NativeActivity
era
a classe recomendada para jogos. GameActivity
a substitui como a classe recomendada
para jogos e é compatível com versões anteriores à API de nível 19.
Para ver uma amostra que integra a GameActivity, consulte o repositório games-samples.
Antes de começar
Consulte as versões de GameActivity
para
receber uma distribuição.
Configurar o build
No Android, uma Activity
serve como ponto de
entrada para o jogo e também fornece a
Window
para desenhar. Muitos jogos estendem
essa Activity
com a própria classe Java ou Kotlin para driblar limitações em
NativeActivity
enquanto usam o código JNI
para fazer a ponte
com o código de jogo em C ou C++.
GameActivity
oferece os seguintes recursos:
Herda de
AppCompatActivity
, permitindo que você use componentes de arquitetura do Android Jetpack.É renderizada em uma
SurfaceView
, que permite interagir com qualquer outro elemento da IU do Android.Processa eventos de atividade Java. Isso permite que qualquer elemento da IU do Android, como um
EditText
, umaWebView
ou umAd
, seja integrado ao seu jogo por uma interface C.Oferece uma API C semelhante às bibliotecas
NativeActivity
eandroid_native_app_glue
.
A GameActivity
é distribuída como um ARchive do Android
(AAR). Esse AAR contém a classe Java que
você usa no
AndroidManifest.xml
, além do código-fonte C
e C++ que conecta a parte Java da GameActivity
à
implementação em C/C++ do app. Se você estiver usando a GameActivity
1.2.2 ou mais recente, a biblioteca
C/C++ estática também será fornecida. Sempre que aplicável, recomendamos usar
a biblioteca estática em vez do código-fonte.
Inclua esses arquivos de origem ou a biblioteca estática como parte do
processo de compilação usando
Prefab
,
que expõe bibliotecas nativas e código-fonte ao seu projeto do CMake ou build do NDK (em inglês).
Siga as instruções na página Jetpack Android Games para adicionar a dependência da biblioteca
GameActivity
ao arquivobuild.gradle
do jogo.Ative o prefab fazendo o seguinte com a versão do plug-in do Android para Gradle (AGP) 4.1+:
- Adicione o seguinte ao bloco
android
do arquivobuild.gradle
do módulo:
buildFeatures { prefab true }
- Escolha uma versão prefab
e a defina como o arquivo
gradle.properties
:
android.prefabVersion=2.0.0
Se você usa versões anteriores do AGP, siga a documentação do prefab para as instruções de configuração correspondentes.
- Adicione o seguinte ao bloco
Importe a biblioteca estática C/C++ ou o código-fonte C/++ para seu projeto da seguinte maneira:
Biblioteca estática
No arquivo
CMakeLists.txt
do projeto, importe a biblioteca estáticagame-activity
para o 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-fonte
No arquivo
CMakeLists.txt
do projeto, importe o pacotegame-activity
e adicione-o ao destino: O pacotegame-activity
requerlibandroid.so
. Portanto, se ele estiver ausente, precisará ser importado.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
Além disso, inclua os seguintes arquivos no
CmakeLists.txt
do seu projeto:GameActivity.cpp
,GameTextInput.cpp
eandroid_native_app_glue.c
.
Como o Android inicia sua atividade
O sistema Android executa o código na instância de atividade invocando métodos de callback que correspondem a estágios específicos do ciclo de vida da atividade. Para que o Android inicie sua atividade e o jogo, é necessário declarar a atividade com os atributos adequados no manifesto do Android. Para mais informações, consulte Introdução às atividades.
Manifesto do Android
Todo projeto de aplicativo precisa ter um arquivo AndroidManifest.xml na raiz do conjunto de origem do projeto. O arquivo de manifesto descreve informações essenciais sobre o app para as ferramentas de compilação do Android, para o sistema operacional Android e para o Google Play. Esse conteúdo inclui o seguinte:
Nome do pacote e ID do aplicativo para identificar seu jogo de maneira exclusiva no Google Play
Componentes do aplicativo, como atividades, serviços, broadcast receivers e provedores de conteúdo
Permissões para acessar partes protegidas do sistema ou de outros apps.
Compatibilidade com dispositivo para especificar os requisitos de hardware e software do jogo.
Nome da biblioteca nativa de
GameActivity
eNativeActivity
(o padrão é libmain.so).
Implementar GameActivity no jogo
Crie ou identifique sua classe Java de atividade principal, que é aquela especificada no elemento
activity
dentro do arquivoAndroidManifest.xml
. Mude essa classe para estenderGameActivity
do pacotecom.google.androidgamesdk
:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
Verifique se a biblioteca nativa está carregada no início usando um bloco 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"); } ... }
Adicione a biblioteca nativa a
AndroidManifest.xml
se o nome da biblioteca não for o padrão (libmain.so
):<meta-data android:name="android.app.lib_name" android:value="android-game" />
Implementar android_main
android_native_app_glue
é uma biblioteca de códigos-fonte que seu jogo usa para gerenciar eventos de ciclo de vida daGameActivity
em uma linha de execução separada para evitar bloqueios na linha de execução principal. Ao usar a biblioteca, Registre o callback para processar eventos de ciclo de vida, como entrada por toque. eventos. O arquivoGameActivity
inclui a própria versão da bibliotecaandroid_native_app_glue
. Portanto, não é possível usar a versão incluída em versões do NDK. Se os jogos estiverem usando a bibliotecaandroid_native_app_glue
incluída no NDK, mude para a versãoGameActivity
.Depois de adicionar o código-fonte da biblioteca
android_native_app_glue
ao projeto, ele interage comGameActivity
. Implemente uma função denominadaandroid_main
, que é chamada pela biblioteca e usada como ponto de entrada para o jogo. Ela recebe uma estrutura chamadaandroid_app
. Isso pode variar no seu jogo e no mecanismo. Veja um exemplo:#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; }
Processe
android_app
no loop de jogo principal, como a pesquisa e o processamento de eventos de ciclo do app definidos em NativeAppGlueAppCmd. Por exemplo, o snippet a seguir registra a função_hand_cmd_proxy
como o gerenciadorNativeAppGlueAppCmd
. Em seguida, pesquisa os eventos de ciclo do app e os envia ao gerenciador registrado emandroid_app::onAppCmd
para processamento: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(); } } }
Para ler mais, estude a implementação do Endless Tunnel Exemplo do NDK. A principal diferença é o modo de processar eventos, conforme mostrado na próxima seção.
Processar eventos
Para permitir que os eventos de entrada cheguem ao seu app, crie e registre seus filtros de
eventos com android_app_set_motion_event_filter
e android_app_set_key_event_filter
.
Por padrão, a biblioteca native_app_glue
só permite eventos de movimento da
entrada
SOURCE_TOUCHSCREEN. Consulte o documento de referência
e o código para implementação de android_native_app_glue
para ver mais detalhes.
Para processar eventos de entrada, acesse uma referência ao android_input_buffer
com
android_app_swap_input_buffers()
no loop de jogo. Eles contêm os eventos de movimento e de chave que ocorreram desde a
pesquisa. O número de eventos contidos é armazenado em motionEventsCount
e
keyEventsCount
, respectivamente.
Faça a iteração e o processamento de cada evento no seu loop de jogo. Neste exemplo, o código a seguir faz a iteração de
motionEvents
e os processa comhandle_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); }
Consulte a Exemplo do GitHub para a implementação do
_cookEventForPointerIndex()
e de outros funções relacionadas.Quando terminar, não se esqueça de limpar a fila de eventos que você acabou de processar:
android_app_clear_motion_events(mApp);
Outros recursos
Para saber mais sobre GameActivity
, consulte:
- Notas da versão da GameActivity e AGDK
- Usar GameTextInput na GameActivity
- Guia de migração da NativeActivity
- Documentação de referência da GameActivity
- Implementação da GameActivity
Para informar bugs ou solicitar novos recursos à GameActivity, use o Issue Tracker da GameActivity (link em inglês).