GameActivity Android Game Development Kit 的一部分。
GameActivity
可簡化使用重要 API 的程序,協助您將 C 或 C++ 遊戲引入 Android。
如需整合 GameActivity 的範例,請參閱「遊戲範例存放區」。
設定版本
在 Android 裝置上,Activity
除了是遊戲的進入點,也提供了可在當中繪圖的 Window
。許多遊戲會採用自己的 Java 或 Kotlin 類別來擴充這個 Activity
,以打破 NativeActivity
的限制,同時使用 JNI
程式碼橋接至其 C 或 C++ 遊戲程式碼。
GameActivity
提供下列功能:
沿用
AppCompatActivity
設定,您可以使用 Android Jetpack 架構元件。轉譯為
SurfaceView
,讓您與任何其他 Android UI 元素互動。處理 Java 活動事件。允許任何 Android UI 元素 (例如
EditText
、WebView
或Ad
),使用 C 介面整合至您的遊戲。提供類似
NativeActivity
和android_native_app_glue
程式庫的 C API。
GameActivity
會發布為 Android Archive (AAR)。這個 AAR 包含您將在 AndroidManifest.xml
中使用的 Java 類別,以及用於實作 GameActivity
的原生功能的 C 和 C++ 原始碼。請在建構程序中透過 Prefab
加入這些來源檔案,以便將原生資料庫和原始碼提供給 CMake 專案或 NDK 建構系統。
按照 Jetpack Android Games 頁面上的操作說明,將
GameActivity
程式庫依附元件新增至遊戲的build.gradle
檔案。透過 Android 外掛程式 (AGP) 4.1 以上版本執行下列操作,即可啟用 prefab:
- 將以下內容加入模組
build.gradle
檔案的android
區塊中:
buildFeatures { prefab true }
- 選擇 Prefab 版本,並將其設為
gradle.properties
檔案:
android.prefabVersion=2.0.0
如果您使用的是較舊的 AGP 版本,請按照「Prefab 說明文件」取得對應的設定操作說明。
- 將以下內容加入模組
在專案的
CMakeLists.txt
檔案中匯入game-activity
套件,並將該套件加入目標。game-activity 需要libandroid.so
,如果該檔案不在目標內,請新增該檔案:find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
在遊戲的現有
.cpp
檔案或新的.cpp
檔案中新增下列程式碼,以加入GameActivity
、GameTextInput
和隨附的原生 glue 導入項目:#include <game-activity/GameActivity.cpp> #include <game-text-input/gametextinput.cpp> extern "C" { #include <game-activity/native_app_glue/android_native_app_glue.c> }
Android 如何啟動您的活動
Android 系統會叫用與活動生命週期特定階段相應的回呼方法,以執行活動執行個體中的程式碼。為了讓 Android 啟動您的活動並開始遊戲,您必須在 Android 資訊清單中使用適當的屬性宣告活動。詳情請參閱「活動簡介」一節。
Android 資訊清單
每個應用程式專案都必須在專案來源集的根目錄中有一個 AndroidManifest.xml 檔案。資訊清單檔案會將您應用程式的基本資訊提供給 Android 建構工具、Android 作業系統和 Google Play。其中包括:
套件名稱和應用程式 ID,可明確識別 Google Play 上的遊戲。
應用程式元件,例如活動、服務、廣播接收器和內容供應者。
權限,可存取系統或其他應用程式受保護的部分。
裝置相容性,可指定遊戲的硬體和軟體需求。
GameActivity
和NativeActivity
的原生資料庫名稱 (預設為 libmain.so)。
在遊戲中實作 GameActivity
建立或識別主要活動 Java 類別 (在
AndroidManifest.xml
檔案的activity
元素中指定的類別)。變更這個類別,以透過com.google.androidgamesdk
套件擴充GameActivity
:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
確保您的原生資料庫在開始時使用靜態區塊載入:
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"); } ... }
如果資料庫名稱並非預設名稱 (
libmain.so
),請將原生資料庫新增至AndroidManifest.xml
:<meta-data android:name="android.app.lib_name" android:value="android-game" />
實作 android_main
android_native_app_glue
程式庫是原始碼程式庫,您的遊戲會在不同的執行緒中管理GameActivity
生命週期事件,以免受到主執行緒的封鎖。使用程式庫時,您可以註冊回呼來處理生命週期事件,例如觸控輸入事件。GameActivity
封存檔案包含其自身的android_native_app_glue
程式庫版本,因此無法使用 NDK 版本中包含的版本。如果您的遊戲使用的是 NDK 中包含的android_native_app_glue
程式庫,請切換至GameActivity
版本。將
android_native_app_glue
程式庫原始碼新增至專案後,即會與GameActivity
建立連結。實作名為android_main
的函式,這個函式是由程式庫呼叫,並用來當做遊戲的進入點。系統會傳遞稱為android_app
的結構。這可能會因遊戲和引擎而異。範例如下:#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; }
處理主要遊戲迴圈中的
android_app
並對事件進行輪詢。舉例來說: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(); } } }
如要進一步瞭解,請參閱 Endless Tunnel NDK 範例的實作說明。主要差異在於處理事件的方式,如下一節所示。
處理事件
如要處理輸入事件,請參閱陣列 motionEvents
、keyUpEvents
和 keyDownEvents
。這些事件包含自上次清除這些陣列之後發生的事件。包含的事件數分別儲存在 motionEventsCount
、keyUpEventsCount
和 keyDownEventsCount
中。
疊代並處理遊戲迴圈中的各個事件。在這個範例中,以下程式碼會疊代
motionEvents
,並透過handle_event
進行處理:auto inputBuffer = android_app_swap_input_buffers(app); if (inputBuffer && inputBuffer->motionEventsCount) { 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); }
如需
_cooked_event_callback()
的實作說明,請參閱 GitHub 範例。完成後,請記得清除剛處理的事件佇列:
android_app_clear_motion_events(mApp);
參考資料
如要進一步瞭解 GameActivity
,請參閱以下說明:
如要向 GameActivity 回報錯誤或要求新功能,請使用 GameActivity Issue Tracker。