從 NativeActivity 遷移   Android Game Development Kit 提供的一項工具

GameActivity 以 Android 架構的 NativeActivity 為基礎,並加強及新增了幾項功能:

  • 支援 Jetpack 中的 Fragment
  • 新增 TextInput 支援,以協助整合螢幕鍵盤。
  • 處理 GameActivity Java 類別 (而非 NativeActivity onInputEvent 介面) 中的觸控和重要事件。

NativeActivity 遷移至 GameActivity 相對簡單,且需要下列應用程式變更:

  1. 更新 Java 和 C/C++ 建構指令碼。
  2. 整合新的 Java 類別和 C/C++ 原始碼。

本指南的其餘部分詳細說明了必要步驟。進行遷移之前,建議您先參閱「入門指南」,瞭解 GameActivity 運作方式的高階資訊。

Java 版本指令碼更新

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 在發布子版本期間通常需要使用最新的 SDK 版本。

    更新後的 build.gradle 檔案大致如下:

    android {
        compiledSdkVersion 31
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.4.2'
        implementation 'androidx.games:games-activity:1.1.0'
    }
    

Kotlin 或 Java 程式碼更新

NativeActivity 可用來做為啟動活動,並建立全螢幕應用程式。目前,GameActivity 不得做為啟動活動使用。應用程式必須從 GameActivity 衍生類別,並以此做為啟動活動。您必須做出其他設定變更,才能建立全螢幕應用程式。

下列步驟假設您的應用程式使用 NativeActivity 做為啟動活動。如果不是,您可以略過大多數步驟。

  1. 建立 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");
          }
      }
    
  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++ 建構指令碼檔案內部,如 CMakeLists.txt

    add_library(AndroidGame ...)
    

C/C++ 建構指令碼更新

本節的操作說明使用 cmake 做為範例。如果您的應用程式使用 ndk-build,必須將應用程式對應至「ndk-build 說明文件頁面」中說明的對等指令。

目前,GameActivity 的 C/C++ 實作僅限原始碼版本,與 prefab 公用程式一起內建在 AAR 中。原生程式碼包含 GameActivity 的 C/C++ 來源和 native_app_glue 程式碼。必須隨應用程式的 C/C++ 程式碼一起建構。

NativeActivity 應用程式已使用 NDK 內隨附的 native_app_glue 程式碼。必須將其取代為 GameActivity 版本 native_app_glue。此外,這份入門指南內記錄的所有 cmake 步驟均適用:

  • game-activity 加入應用程式的 C/C++ 模組依附元件:

    find_package(game-activity REQUIRED CONFIG)
        ...
    target_link_libraries(AndroidGame 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)
    

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 程式庫有任何疑問或建議,請建立錯誤告訴我們。