Get started with GameActivity Part of Android Game Development Kit.
This guide describes how to set up and integrate
GameActivity
and handle events in your Android
game.
GameActivity
helps you bring your C or
C++ game to Android by simplifying the process of using critical APIs.
Previously NativeActivity
was
the recommended class for games. GameActivity
replaces it as the recommended
class for games, and is backwards compatible to API level 19.
For a sample that integrates GameActivity, see the games-samples repository.
Before you start
See GameActivity
releases to
obtain a distribution.
Set up your build
On Android, an Activity
serves as the entry
point for your game, and also provides the
Window
to draw within. Many games extend
this Activity
with their own Java or Kotlin class to defeat limitations in
NativeActivity
while using JNI
code to bridge
to their C or C++ game code.
GameActivity
offers the following capabilities:
Inherits from
AppCompatActivity
, allowing you to use Android Jetpack Architecture Components.Renders into a
SurfaceView
that allows you to interface with any other Android UI element.Handles Java activity events. This allows any Android UI element (such as a
EditText
, aWebView
or anAd
) to be integrated to your game via a C interface.Offers a C API similar to
NativeActivity
, andandroid_native_app_glue
library.
GameActivity
is distributed as an Android Archive
(AAR). This AAR contains the Java class that
you use in your
AndroidManifest.xml
, as well as the C
and C++ source code that connects the Java side of GameActivity
to the app's
C/C++ implementation. If you're using GameActivity
1.2.2 or later, the C/C++
static library is also provided. Whenever applicable, we recommend that you use
the static library instead of the source code.
Include these source files or the static library as part of your
build process through
Prefab
,
which exposes native libraries and source code to your
CMake project or NDK build.
Follow the instructions at the Jetpack Android Games page to add the
GameActivity
library dependency to your game'sbuild.gradle
file.Enable prefab by doing the following with Android Plugin Version (AGP) 4.1+:
- Add the following to the
android
block of your module'sbuild.gradle
file:
buildFeatures { prefab true }
- Pick a Prefab version,
and set it to the
gradle.properties
file:
android.prefabVersion=2.0.0
If you use earlier AGP versions, follow the prefab documentation for the corresponding configuration instructions.
- Add the following to the
Import either the C/C++ static library or the C/++ source code into your project as follows.
Static library
In your project's
CMakeLists.txt
file, import thegame-activity
static library into thegame-activity_static
prefab module:find_package(game-activity REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PUBLIC log android game-activity::game-activity_static)
Source code
In your project's
CMakeLists.txt
file, import thegame-activity
package and add it to your target. Thegame-activity
package requireslibandroid.so
, so if it's missing, you must also import it.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
Also, include the following files into your project's
CmakeLists.txt
:GameActivity.cpp
,GameTextInput.cpp
, andandroid_native_app_glue.c
.
How Android launches your Activity
The Android system executes code in your Activity instance by invoking callback methods that correspond to specific stages of the activity lifecycle. In order for Android to launch your activity and start your game, you need to declare your activity with the appropriate attributes in the Android Manifest. For more information, see Introduction to Activities.
Android Manifest
Every app project must have an AndroidManifest.xml file at the root of the project source set. The manifest file describes essential information about your app to the Android build tools, the Android operating system, and Google Play. This includes:
Package name and app ID to uniquely identify your game on Google Play.
App Components such as activities, services, broadcast receivers, and content providers.
Permissions to access protected parts of the system or other apps.
Device compatibility to specify hardware and software requirements for your game.
Native library name for
GameActivity
andNativeActivity
(default is libmain.so).
Implement GameActivity in your game
Create or identify your main activity Java class (the one specified in the
activity
element inside yourAndroidManifest.xml
file). Change this class to extendGameActivity
from thecom.google.androidgamesdk
package:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
Make sure your native library is loaded at the start using a static block:
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"); } ... }
Add your native library to
AndroidManifest.xml
if your library name is not the default name (libmain.so
):<meta-data android:name="android.app.lib_name" android:value="android-game" />
Implement android_main
The
android_native_app_glue
library is a source code library that your game uses to manageGameActivity
lifecycle events in a separate thread in order to prevent blocking in your main thread. When using the library, you register the callback to handle lifecycle events, such as touch input events. TheGameActivity
archive includes its own version of theandroid_native_app_glue
library, so you can not use the version included in NDK releases. If your games are using theandroid_native_app_glue
library that's included in the NDK, switch to theGameActivity
version.After you add the
android_native_app_glue
library source code to your project, it interfaces withGameActivity
. Implement a function calledandroid_main
, which is called by the library and used as the entry point for your game. It is passed a structure calledandroid_app
. This may differ for your game and engine. Here's an example:#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; }
Process
android_app
in your main game loop, such as polling and handling app cycle events defined in NativeAppGlueAppCmd. For example, the following snippet registers function_hand_cmd_proxy
as theNativeAppGlueAppCmd
handler, then polls app cycle events, and sends them to the registered handler(inandroid_app::onAppCmd
) for processing: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(); } } }
For further reading, study the implementation of the Endless Tunnel NDK example. The main difference will be how to handle events as shown in the next section.
Handle events
To enable input events to reach your app, create and register your event
filters with android_app_set_motion_event_filter
and android_app_set_key_event_filter
.
By default, native_app_glue
library only allows motion events from
SOURCE_TOUCHSCREEN
input. Make sure to check out the reference doc
and the android_native_app_glue
implmenetation code for the details.
To handle input events, get a reference to the android_input_buffer
with
android_app_swap_input_buffers()
in your game loop. These contain motion events and key events that have happened since the last time it was
polled. The number of events contained is stored in motionEventsCount
, and
keyEventsCount
respectively.
Iterate and handle each event in your game loop. In this example, the following code iterates
motionEvents
and handles them viahandle_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); }
See the GitHub sample for the implementation of the
_cookEventForPointerIndex()
and other related functions.When you are done, remember to clear the queue of events that you have just handled:
android_app_clear_motion_events(mApp);
Additional resources
To learn more about GameActivity
, see the following:
- GameActivity and AGDK release notes.
- Use the GameTextInput in GameActivity.
- NativeActivity migration guide.
- GameActivity reference documentation.
- GameActivity implementation.
To report bugs or request new features to GameActivity, use the GameActivity issue tracker.