GameActivity   Part of Android Game Development Kit.

GameActivity helps you bring your C or C++ game to Android by simplifying the process of using critical APIs.

For a sample that integrates GameActivity, see the games-samples repository.

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:

GameActivity is distributed as an Android Archive (AAR). This AAR contains the Java class that you will use in your AndroidManifest.xml, as well as the C and C++ source code, which implements the native features of GameActivity. Include these source files as part of your build process via Prefab, which exposes native libraries and source code to your CMake project or NDK build.

  1. Follow the instructions at the Jetpack Android Games page to add the GameActivity library dependency to your game's build.gradle file.

  2. Enable prefab by doing the following with Android Plugin Version (AGP) 4.1+:

    • Add the following to the android block of your module's build.gradle file:
        buildFeatures {
            prefab true
        }
    
        android.prefabVersion=2.0.0
    

    If you use earlier AGP versions, follow the prefab documenation for the corresponding configuration instructions.

  3. In your project's CMakeLists.txt file, import the game-activity package and add it to your target (game-activity needs libandroid.so, add it too if it is not there):

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  4. In one of the existing .cpp files in your game, or in a new .cpp file, add the following to include the GameActivity, GameTextInput, and the companion native glue implementation:

    #include <game-activity/GameActivity.cpp>
    #include <game-text-input/gametextinput.cpp>
    extern "C" {
      #include <game-activity/native_app_glue/android_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:

Implement GameActivity in your game

  1. Create or identify your main activity Java class (the one specified in the activity element inside your AndroidManifest.xml file). Change this class to extend GameActivity from the com.google.androidgamesdk package:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. 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");
      }
      ...
    }
    
  3. 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

  1. The android_native_app_glue library is a source code library that your game uses to manage GameActivity 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. The GameActivity archive includes its own version of the android_native_app_glue library, so you can not use the version included in NDK releases. If your games are using the android_native_app_glue library that's included in the NDK, switch to the GameActivity version.

    After you add the android_native_app_glue library source code to your project, it interfaces with GameActivity. Implement a function called android_main, which is called by the library and used as the entry point for your game. It is passed a structure called android_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;
    }
    
  2. Process android_app in your main game loop and poll for events. For example:

    void NativeEngine::GameLoop() {
      mApp->userData = this;
      mApp->onAppCmd = _handle_cmd_proxy;
      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) {
                source->process(mApp, source);
            }
            if (mApp->destroyRequested) {
                return;
            }
        }
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  3. 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 handle input events, read the arrays motionEvents, keyUpEvents and keyDownEvents in your game loop. These contain events that have happened since the last time these arrays were cleared. The number of events contained is stored in motionEventsCount, keyUpEventsCount, and keyDownEventsCount, respectively.

  1. Iterate and handle each event in your game loop. In this example, the following code iterates motionEvents and handles them via handle_event:

    auto inputBuffer = android_app_swap_input_buffers(app);
    if (inputBuffer->motionEventsCount != 0) {
        for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) {
            GameActivityMotionEvent* motionEvent = &inputBuffer->motionEvents[i];
    
            if (motionEvent->pointerCount > 0) {
                int action = motionEvent->action;
                int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
                int ptrIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
    
                if (ptrIndex < motionEvent->pointerCount) {
                    struct CookedEvent ev;
                    memset(&ev, 0, sizeof(ev));
    
                    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
                        actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
                            ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                    } else if (actionMasked == AMOTION_EVENT_ACTION_UP ||
                            actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
                            ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                    } else {
                            ev.type = COOKED_EVENT_TYPE_POINTER_MOVE;
                    }
    
                    ev.motionPointerId = motionEvent->pointers[ptrIndex].id;
                    ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN;
                    ev.motionX = GameActivityPointerAxes_getX(&motionEvent->pointers[ptrIndex]);
                    ev.motionY = GameActivityPointerAxes_getY(&motionEvent->pointers[ptrIndex]);
    
                    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();
                    }
    
                    _cooked_event_callback(&ev);
                }
            }
        }
        android_app_clear_motion_events(inputBuffer);
    }
    
    

    See the GitHub sample for the _cooked_event_callback() implementation.

  2. When you are done, remember to clear the queue of events that you have just handled:

    android_app_clear_motion_events(mApp);
    

References

To learn more about GameActivity, see the following:

To report bugs or request new features to GameActivity, use the GameActivity issue tracker.