GameActivity のスタートガイド Android Game Development Kit の一部。
このガイドでは、Android ゲームで GameActivity
をセットアップして統合し、イベントを処理する方法について説明します。
GameActivity
は、重要な API を使用するプロセスを単純化することにより、C または C++ のゲームを Android に組み込む作業を支援します。以前は、ゲーム用として NativeActivity
クラスが推奨されていました。GameActivity
はそれに代わるゲーム用の推奨クラスであり、API レベル 19 と下位互換性があります。
GameActivity を統合するサンプルについては、games-samples リポジトリをご覧ください。
準備
ディストリビューションの入手については、GameActivity
のリリースをご覧ください。
ビルドをセットアップする
Android では、Activity
はゲームのエントリ ポイントとして機能するだけでなく、内部に描画できる Window
も提供します。多くのゲームは、独自の Java クラスまたは Kotlin クラスでこの Activity
を拡張して NativeActivity
の制限を克服する一方で、JNI
コードを使用して C または C++ ゲームのコードにブリッジします。
GameActivity
は次の機能を提供します。
AppCompatActivity
からの継承により、Android Jetpack アーキテクチャ コンポーネントを使用できるようにします。他の Android UI 要素とのやり取りを可能にする
SurfaceView
へのレンダリングを行います。Java アクティビティ イベントを処理します。これにより、C インターフェースを介して任意の Android UI 要素(
EditText
、WebView
、Ad
など)をゲームに統合できます。NativeActivity
に似た C API とandroid_native_app_glue
ライブラリを提供します。
GameActivity
は Android Archive(AAR)として配布されます。この AAR には、AndroidManifest.xml
で使用する Java クラスと、GameActivity
の Java 側をアプリの C/C++ 実装に接続する C および C++ ソースコードが含まれています。GameActivity
1.2.2 以降を使用している場合は、C/C++ 静的ライブラリも提供されます。該当する場合は、ソースコードではなく静的ライブラリを使用することをおすすめします。
ネイティブ ライブラリとソースコードを CMake プロジェクトまたは NDK ビルドに公開する Prefab
を通じて、これらのソースファイルまたは静的ライブラリをビルドプロセスの一部として組み込みます。
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 のドキュメントで対応する設定手順をご確認ください。
- モジュールの
次のように、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)
また、プロジェクトの
CmakeLists.txt
にGameActivity.cpp
、GameTextInput.cpp
、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
を処理し、NativeAppGlueAppCmd で定義されているアプリ サイクル イベントのポーリングや処理などを行います。 たとえば、次のスニペットは関数_hand_cmd_proxy
をNativeAppGlueAppCmd
ハンドラとして登録し、アプリ サイクル イベントをポーリングして、登録したハンドラ(android_app::onAppCmd
内)にそれらのイベントを処理のために送信します。void NativeEngine::GameLoop() { mApp->userData = this; mApp->onAppCmd = _handle_cmd_proxy; // register your command handler. 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) { // process events, native_app_glue internally sends the outstanding // application lifecycle events to mApp->onAppCmd. source->process(source->app, source); } if (mApp->destroyRequested) { return; } } if (IsAnimating()) { DoFrame(); } } }
詳細については、エンドレス トンネルの実装をご覧ください。 NDK の例。次のセクションで説明するように、主な違いはイベントの処理方法にあります。
イベントを処理する
入力イベントがアプリに届くようにするには、イベント フィルタを作成し、android_app_set_motion_event_filter
と android_app_set_key_event_filter
で登録します。デフォルトでは、native_app_glue
ライブラリは SOURCE_TOUCHSCREEN 入力からのモーション イベントのみを許可します。詳しくは、リファレンス ドキュメントと android_native_app_glue
の実装コードをご確認ください。
入力イベントを処理するには、ゲームループで android_app_swap_input_buffers()
を使用して android_input_buffer
への参照を取得します。入力イベントには、前回のポーリング以降に発生したモーション イベントとキーイベントが含まれます。含まれるイベントの数は、それぞれ motionEventsCount
と keyEventsCount
に格納されています。
ゲームループ内で反復処理により各イベントを処理します。次のコード例では、
motionEvents
に対する反復処理により、それらをhandle_event
で処理しています。android_input_buffer* 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) { const int action = motionEvent->action; const int actionMasked = action & AMOTION_EVENT_ACTION_MASK; // Initialize pointerIndex to the max size, we only cook an // event at the end of the function if pointerIndex is set to a valid index range uint32_t pointerIndex = GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT; struct CookedEvent ev; memset(&ev, 0, sizeof(ev)); ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN; 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(); } switch (actionMasked) { case AMOTION_EVENT_ACTION_DOWN: pointerIndex = 0; ev.type = COOKED_EVENT_TYPE_POINTER_DOWN; break; case AMOTION_EVENT_ACTION_POINTER_DOWN: pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); ev.type = COOKED_EVENT_TYPE_POINTER_DOWN; break; case AMOTION_EVENT_ACTION_UP: pointerIndex = 0; ev.type = COOKED_EVENT_TYPE_POINTER_UP; break; case AMOTION_EVENT_ACTION_POINTER_UP: pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); ev.type = COOKED_EVENT_TYPE_POINTER_UP; break; case AMOTION_EVENT_ACTION_MOVE: { // Move includes all active pointers, so loop and process them here, // we do not set pointerIndex since we are cooking the events in // this loop rather than at the bottom of the function ev.type = COOKED_EVENT_TYPE_POINTER_MOVE; for (uint32_t i = 0; i < motionEvent->pointerCount; ++i) { _cookEventForPointerIndex(motionEvent, callback, ev, i); } break; } default: break; } // Only cook an event if we set the pointerIndex to a valid range, note that // move events cook above in the switch statement. if (pointerIndex != GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT) { _cookEventForPointerIndex(motionEvent, callback, ev, pointerIndex); } } } android_app_clear_motion_events(inputBuffer); }
詳しくは、 GitHub のサンプル 実装のために、
_cookEventForPointerIndex()
などの 使用できます。完了したら、処理したイベントのキューを忘れずにクリアします。
android_app_clear_motion_events(mApp);
参考情報
GameActivity
について詳しくは、以下をご覧ください。
- GameActivity と AGDK のリリースノート
- GameActivity で GameTextInput を使用する
- NativeActivity の移行ガイド
- GameActivity リファレンス ドキュメント
- GameActivity の実装
GameActivity に関連するバグの報告または機能のリクエストを行うには、GameActivity の Issue Tracker を使用してください。