Introdução à 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, umaWebViewou umAd, seja integrado ao seu jogo por uma interface C.Oferece uma API C semelhante às bibliotecas
NativeActivityeandroid_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
GameActivityao arquivobuild.gradledo 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
androiddo arquivobuild.gradledo módulo:
buildFeatures { prefab true }- Escolha uma versão prefab
e a defina como o arquivo
gradle.properties:
android.prefabVersion=2.0.0Se 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.txtdo projeto, importe a biblioteca estáticagame-activitypara 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.txtdo projeto, importe o pacotegame-activitye adicione-o ao destino: O pacotegame-activityrequerlibandroid.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.txtdo seu projeto:GameActivity.cpp,GameTextInput.cppeandroid_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
GameActivityeNativeActivity(o padrão é libmain.so).
Implementar GameActivity no jogo
Crie ou identifique sua classe Java de atividade principal, que é aquela especificada no elemento
activitydentro do arquivoAndroidManifest.xml. Mude essa classe para estenderGameActivitydo 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.xmlse 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 daGameActivityem uma linha de execução separada para evitar bloqueios na linha de execução principal. Ao usar a biblioteca, você registra o callback para processar eventos de ciclo de vida, como eventos de entrada por toque. O arquivoGameActivityinclui 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_glueincluída no NDK, mude para a versãoGameActivity.Depois de adicionar o código-fonte da biblioteca
android_native_app_glueao 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_appno 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_proxycomo o gerenciadorNativeAppGlueAppCmd. Em seguida, pesquisa os eventos de ciclo do app e os envia ao gerenciador registrado emandroid_app::onAppCmdpara 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_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(); } } }Para saber mais, estude a implementação do exemplo do NDK Endless Tunnel. 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
motionEventse 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 o exemplo do GitHub (link em inglês) para ver a implementação de
_cookEventForPointerIndex()e outras 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).