从 NativeActivity 迁移   Android Game Development Kit 的一部分。

本页介绍了如何在 Android 游戏项目中从 NativeActivity 迁移到 GameActivity

GameActivity 基于 Android 框架中的 NativeActivity,并包含以下增强功能和新功能:

  • 支持 Jetpack 中的 Fragment
  • 添加了 TextInput 支持,以便集成软键盘。
  • GameActivity Java 类(而非 NativeActivity onInputEvent 接口)中处理触摸和按键事件。

在迁移之前,我们建议您阅读入门指南,其中介绍了如何在项目中设置和集成 GameActivity

Java build 脚本更新

GameActivity 以 Jetpack 库的形式分发。请务必执行入门指南中所述的 Gradle 脚本更新步骤:

  1. 在项目的 gradle.properties 文件中启用 Jetpack 库:

    android.useAndroidX=true
    
  2. (可选)在同一个 gradle.properties 文件中指定 Prefab 版本,例如:

    android.prefabVersion=2.0.0
    
  3. 在应用的 build.gradle 文件中启用 Prefab 功能:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. GameActivity 依赖项添加到您的应用:

    1. 添加 coregames-activity 库。
    2. 如果当前支持的最低 API 级别低于 16,请至少将其更新为 16。
    3. 将已编译的 SDK 版本更新为 games-activity 库所需的版本。Jetpack 通常要求在构建发布 build 时使用最新的 SDK 版本。

    更新后的 build.gradle 文件可能如下所示:

    android {
        compiledSdkVersion 33
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.9.0'
        implementation 'androidx.games:games-activity:1.2.2'
    }
    

Kotlin 或 Java 代码更新

NativeActivity 可用作启动 activity 并创建全屏应用。目前,GameActivity 不能用作启动 activity。应用必须从 GameActivity 派生一个类,并将其用作启动 activity。您还必须进行其他配置更改,才能创建全屏应用。

以下步骤假定您的应用使用 NativeActivity 作为启动 activity。如果不是这样,您可以跳过其中的大多数步骤。

  1. 创建 Kotlin 或 Java 文件以托管新的启动 activity。例如,以下代码会将 MainActivity 创建为启动 activity 并加载应用的主原生库 libAndroidGame.so

    Kotlin

    class MainActivity : GameActivity() {
       override fun onResume() {
           super.onResume()
           // Use the function recommended from the following page:
           // https://d.android.com/training/system-ui/immersive
           hideSystemBars()
       }
       companion object {
           init {
               System.loadLibrary("AndroidGame")
           }
       }
    }
    

    Java

      public class MainActivity extends GameActivity {
          protected void onResume() {
              super.onResume();
              // Use the function recommended from
              // https://d.android.com/training/system-ui/immersive
              hideSystemBars();
          }
          static {
              System.loadLibrary("AndroidGame");
          }
      }
    
  2. res\values\themes.xml 文件中创建全屏应用主题:

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar">
            <item name="android:windowFullscreen">true</item>
            <item name="android:windowContentOverlay">@null</item>"
        </style>
    </resources>
    
  3. AndroidManifest.xml 文件中将该主题应用到应用:

    <application  android:theme=”@style/Application.Fullscreen”>
         <!-- other configurations not listed here. -->
    </application>
    

    如需了解全屏模式的详细说明,请参阅沉浸式指南以及 games-samples 代码库中的示例实现。

本迁移指南不会更改原生库的名称。如果您更改了它,请确保在以下三个位置,原生库名称保持一致:

  • Kotlin 或 Java 代码:

    System.loadLibrary(“AndroidGame”)
    
  • AndroidManifest.xml

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • 在 C/C++ build 脚本文件内,例如 CMakeLists.txt

    add_library(AndroidGame ...)
    

C/C++ build 脚本更新

这一部分中的说明以 cmake 为例。如果您的应用使用 ndk-build,您需要将其映射到 ndk-build 文档页面中介绍的等效命令。

GameActivity 的 C/C++ 实现一直提供源代码版本。 对于 1.2.2 版及更高版本,则提供了一个静态库版本。推荐的版本类型是静态库。

此版本使用 prefab 实用程序打包在 AAR 中。原生代码包含 GameActivity 的 C/C++ 源代码和 native_app_glue 代码。它们需要与应用的 C/C++ 代码一起构建。

NativeActivity 应用已使用 NDK 中提供的 native_app_glue 代码。您必须将其替换为 GameActivity 的 native_app_glue 版本。除此之外,入门指南中所述的所有 cmake 步骤都适用:

  • 如下所示,将 C/C++ 静态库或 C/++ 源代码导入您的项目中。

    静态库

    在项目的 CMakeLists.txt 文件中,将 game-activity 静态库导入 game-activity_static Prefab 模块:

    find_package(game-activity REQUIRED CONFIG)
    target_link_libraries(${PROJECT_NAME} PUBLIC log android
    game-activity::game-activity_static)
    

    源代码

    在项目的 CMakeLists.txt 文件中,导入 game-activity 软件包并将其添加到目标中。game-activity 软件包需要 libandroid.so,因此如果后者缺失,您还必须导入它。

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  • 移除对 NDK 的 native_app_glue 代码的所有引用,例如:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • 如果您使用的是源代码版本,请添加 GameActivity 源文件。否则,请跳过此步骤。

    get_target_property(game-activity-include
                        game-activity::game-activity
                        INTERFACE_INCLUDE_DIRECTORIES)
    add_library(${PROJECT_NAME} SHARED
        main.cpp
        ${game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c
        ${game-activity-include}/game-activity/GameActivity.cpp
        ${game-activity-include}/game-text-input/gametextinput.cpp)
    

解决 UnsatisfiedLinkError 问题

如果您遇到 com.google.androidgamesdk.GameActivity.initializeNativeCode() 函数的 UnsatsifiedLinkError,请将以下代码添加到 CMakeLists.txt 文件中:

set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")

C/C++ 源代码更新

您可以按照以下步骤将应用中的 NativeActivity 引用替换为 GameActivity

  • 使用通过 GameActivity 发布的 native_app_glue。搜索使用的所有 android_native_app_glue.h 并将其替换为:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • 将动作事件过滤器和按键事件过滤器均设置为 NULL,以便您的应用可以从所有输入设备接收输入事件。通常,您可以通过 android_main() 函数来实现此目的:

    void android_main(android_app* app) {
        ... // other init code.
    
        android_app_set_key_event_filter(app, NULL);
        android_app_set_motion_event_filter(app, NULL);
    
        ... // additional init code, and game loop code.
    }
    
  • 移除 AInputEvent 相关代码,并将其替换为 GameActivity 的 InputBuffer 实现:

    while (true) {
        // Read all pending events.
        int events;
        struct android_poll_source* source;
    
        // If not animating, block forever waiting for events.
        // If animating, loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                                (void**)&source)) >= 0) {
           // Process this app cycle or inset change event.
           if (source) {
               source->process(source->app, source);
           }
    
              ... // Other processing.
    
           // Check if app is exiting.
           if (state->destroyRequested) {
               engine_term_display(&engine);
               return;
           }
        }
        // Process input events if there are any.
        engine_handle_input(state);
    
       if (engine.animating) {
           // Draw a game frame.
       }
    }
    
    // Implement input event handling function.
    static int32_t engine_handle_input(struct android_app* app) {
       auto* engine = (struct engine*)app->userData;
       auto ib = android_app_swap_input_buffers(app);
       if (ib && ib->motionEventsCount) {
           for (int i = 0; i < ib->motionEventsCount; i++) {
               auto *event = &ib->motionEvents[i];
               int32_t ptrIdx = 0;
               switch (event->action & AMOTION_EVENT_ACTION_MASK) {
                   case AMOTION_EVENT_ACTION_POINTER_DOWN:
                   case AMOTION_EVENT_ACTION_POINTER_UP:
                       // Retrieve the index for the starting and the ending of any secondary pointers
                       ptrIdx = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
                   case AMOTION_EVENT_ACTION_DOWN:
                   case AMOTION_EVENT_ACTION_UP:
                       engine->state.x = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X);
                       engine->state.y = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y);
                       break;
                    case AMOTION_EVENT_ACTION_MOVE:
                    // Process the move action: the new coordinates for all active touch pointers
                    // are inside the event->pointers[]. Compare with our internally saved
                    // coordinates to find out which pointers are actually moved. Note that there is
                    // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there
                    // might be multiple pointers moved at the same time).
                        ...
                       break;
               }
           }
           android_app_clear_motion_events(ib);
       }
    
       // Process the KeyEvent in a similar way.
           ...
    
       return 0;
    }
    
  • 查看并更新附加到 NativeActivity 的 AInputEvent 的逻辑。如上一步所示,GameActivity 的 InputBuffer 是在 ALooper_pollAll() 循环之外处理的。

  • 将使用的 android_app::activity->clazz 替换为 android_app:: activity->javaGameActivity。GameActivity 会重命名 Java GameActivity 实例。

其他步骤

前面的步骤涵盖了 NativeActivity 的功能,但 GameActivity 还包含您可能想要使用的其他功能:

我们建议您了解这些功能,并在适当情况下为您的游戏采用这些功能。

如果您对 GameActivity 或其他 AGDK 库有任何疑问或建议,请创建 bug 告诉我们。