从 NativeActivity 迁移 Android Game Development Kit 的一部分。
本页介绍了如何在 Android 游戏项目中从 NativeActivity
迁移到 GameActivity
。
GameActivity
基于 Android 框架中的 NativeActivity
,并包含以下增强功能和新功能:
- 支持 Jetpack 中的
Fragment
。 - 添加了
TextInput
支持,以便集成软键盘。 - 在
GameActivity
Java 类(而非NativeActivity
onInputEvent
接口)中处理触摸和按键事件。
在迁移之前,我们建议您阅读入门指南,其中介绍了如何在项目中设置和集成 GameActivity
。
Java build 脚本更新
GameActivity
以 Jetpack 库的形式分发;请务必遵循入门指南中所述的 Gradle 脚本更新步骤:
在项目的
gradle.properties
文件中启用 Jetpack 库:android.useAndroidX=true
(可选)在同一个
gradle.properties
文件中指定 Prefab 版本,例如:android.prefabVersion=2.0.0
在应用的
build.gradle
文件中启用 Prefab 功能:android { ... // other configurations buildFeatures.prefab true }
将
GameActivity
依赖项添加到您的应用:- 添加
core
和games-activity
库。 - 如果当前支持的最低 API 级别低于 16,请至少将其更新为 16。
- 将已编译的 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。如果不是这样,您可以跳过其中的大多数步骤。
创建 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"); } }
在
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>
在
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 会重命名 JavaGameActivity
实例。
其他步骤
前面的步骤涵盖了 NativeActivity 的功能,但 GameActivity
还包含您可能想要使用的其他功能:
- TextInput。
- 游戏控制器。
- fragment。
- 在 NativeAppGlueAppCmd 中定义的新窗口 InSets 命令。
我们建议您了解这些功能,并在适当情况下为您的游戏采用这些功能。
如果您对 GameActivity 或其他 AGDK 库有任何疑问或建议,请创建 bug 告诉我们。