NativeActivity から移行する   Android Game Development Kit の一部。

このページでは、Android ゲーム プロジェクトの NativeActivityGameActivity に移行する方法について説明します。

GameActivity は Android フレームワークの NativeActivity をベースにしていますが、次のような機能強化と新機能があります。

  • Jetpack の Fragment をサポートします。
  • ソフト キーボードの統合を容易にするために、TextInput のサポートが追加されています。
  • NativeActivity onInputEvent インターフェースではなく、GameActivity Java クラスでタッチイベントとキーイベントを処理します。

移行を行う前に、スタートガイドを確認することをおすすめします。このガイドでは、プロジェクトに 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. core ライブラリと games-activity ライブラリを追加します。
    2. 現在サポートしている最小 API レベルが 16 未満の場合は、16 以上に更新します。
    3. コンパイル済み 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 を使用していることを前提としています。そうでない場合は、ほとんどをスキップできます。

  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 リポジトリの実装例をご覧ください。

この移行ガイドでは、ネイティブ ライブラリ名を変更しません。変更する場合は、ネイティブ ライブラリ名が次の 3 つの場所で一致していることを確認してください。

  • 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 以降では、静的ライブラリ リリースも提供されています。おすすめのリリースタイプは静的ライブラリです。

リリースは prefab ユーティリティを使用して AAR 内にパックされています。ネイティブ コードには、GameActivity の C/C++ ソースと native_app_glue コードが含まれます。これらは、アプリの C/C++ コードとともにビルドする必要があります。

NativeActivity アプリでは、NDK に付属の native_app_glue コードがすでに使用されています。これを GameActivity 版の native_app_glue に置き換える必要があります。それ以外は、スタートガイドに記載されているすべての cmake の手順が適用されます。

  • 次のように、C/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 ライブラリに関する質問や提案がある場合は、バグを登録してお知らせください。