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. Make sure gradle.properties contains the following lines:

    # Tell Android Studio we are using AndroidX.
    android.useAndroidX=true
    # Use Prefab 1.1.2 or higher, which contains a fix for "header only" libs.
    android.prefabVersion=1.1.2
    # Required only if you're using Android Studio 4.0 (4.1 is recommended).
    # android.enablePrefab=true
    
  3. In your project's CMakeLists.txt file, import the game-activity package and add it to your target:

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... game-activity::game-activity)
    
  4. In one of the .cpp files in your game, add the following line to include the GameActivity implementation:

    #include "game-activity/GameActivity.cpp"
    
  5. In one of the .cpp files in your game, add the following line to include the GameTextInput implementation:

    #include <game-text-input/gametextinput.cpp>
    
  6. Compile and run the app. If you have CMake errors, verify the AAR and the build.gradle files are properly set up. If the #include file is not found, verify your CMakeLists.txt configuration file.

  7. Create a .c file and add it as part of your build in your CMake file. In this file, add the following line:

    #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 "game" depends on your CMake configuration.
        System.loadLibrary("game");
      }
      ...
    }
    

Implement android_main

  1. The android_native_app_glue library is a static helper library that your game uses to manage NativeActivity lifecycle events in a separate thread in order to prevent blocking in your main thread. Some of the event types are callbacks, input events, and touch input events.

    After you add the android_native_app_glue library, it interfaces with GameActivity. Implement a function called android_main, which will be called by the library and be used as the entry point for your game. It is passed a structure called android_app. This may differ for your game and engine. In this example:

    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->onInputEvent = _handle_input_proxy;
      mApp->motionEventsCount = 0;
      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:

    for(size_t i = 0; i < mApp->motionEventsCount; ++i) {
      GameActivityMotionEvent* motionEvent = mApp->motionEvents[i];
    
      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;
    
      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 = GameActivityPointerInfo_getX(
        &motionEvent->pointers[ptrIndex]);
      ev.motionY = GameActivityPointerInfo_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();
      }
    
      handle_event(&ev);
    }
    
  2. When you are done, remember to clear the queue of events that you have just handled:

    android_app_clear_motion_events(mApp);