從 NativeActivity 遷移 Android Game Development Kit 提供的一項工具
本頁面說明如何將 Android 遊戲專案中的 NativeActivity
改成 GameActivity
。
GameActivity
以 Android 架構的 NativeActivity
為基礎,包含以下強化項目與新功能:
- 支援 Jetpack 中的
Fragment
。 - 新增
TextInput
支援,以協助整合螢幕鍵盤。 - 處理
GameActivity
Java 類別 (而非NativeActivity
onInputEvent
介面) 中的觸控和重要事件。
遷移前,建議您閱讀入門指南,瞭解如何在專案中設定及整合 GameActivity
。
Java 版本指令碼更新
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 在發布子版本期間通常需要使用最新的 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
可用來做為啟動活動,並建立全螢幕應用程式。目前,GameActivity 不得做為啟動活動使用。應用程式必須從 GameActivity
衍生類別,並以此做為啟動活動。您必須做出其他設定變更,才能建立全螢幕應用程式。
下列步驟假設您的應用程式使用 NativeActivity
做為啟動活動。如果不是,您可以略過大多數步驟。
建立 Kotlin 或 Java 檔案來代管新的啟動活動。舉例來說,下列程式碼會建立
MainActivity
做為啟動活動,並載入應用程式的主要原生程式庫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++ 建構指令碼檔案內部,例如
CMakeLists.txt
:add_library(AndroidGame ...)
C/C++ 建構指令碼更新
本節的操作說明使用 cmake
做為範例。如果您的應用程式使用 ndk-build
,請務必對照 ndk-build 說明文件頁面採用相應指令。
我們已針對 GameActivity 提供 C/C++ 實作原始碼,而且在 1.2.2 a 以上版本中提供了靜態資料庫。靜態資料庫是建議的發布類型。
這些釋出內容與 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
提供的其他功能,:
- 文字輸入內容。
- 遊戲控制器。
- 片段。
- 在 NativeAppGlueAppCmd 中定義的新視窗 InSet 指令。
建議您瞭解這些功能,並視需要在遊戲中使用。
如對 GameActivity 或其他 AGDK 程式庫有任何疑問或建議,敬請建立錯誤告訴我們。