Mulai menggunakan GameActivity Bagian dari Android Game Development Kit.

Panduan ini menjelaskan cara menyiapkan dan mengintegrasikan GameActivity serta menangani peristiwa di game Android Anda.

GameActivity membantu Anda menghadirkan game C atau C++ ke Android dengan menyederhanakan proses penggunaan API penting. Sebelumnya, NativeActivity adalah class yang direkomendasikan untuk game. GameActivity menggantikannya sebagai class yang direkomendasikan untuk game dan memiliki kompatibilitas mundur hingga API level 19.

Untuk mengetahui contoh yang mengintegrasikan GameActivity, lihat repositori contoh game.

Sebelum memulai

Lihat rilis GameActivity untuk mendapatkan distribusi.

Menyiapkan build

Di Android, Activity berfungsi sebagai titik masuk untuk game Anda, dan juga menyediakan Window untuk digambar di dalamnya. Banyak game memperluas Activity ini dengan class Java atau Kotlin-nya sendiri untuk membatalkan batasan di NativeActivity saat menggunakan kode JNI untuk dihubungkan ke kode game C atau C++ mereka.

GameActivity menawarkan kemampuan berikut:

GameActivity didistribusikan sebagai Android Archive (AAR). AAR ini berisi class Java yang Anda gunakan dalam AndroidManifest.xml, serta kode sumber C dan C++ yang menghubungkan sisi Java dari GameActivity ke implementasi C/C++ aplikasi. Jika Anda menggunakan GameActivity 1.2.2 atau yang lebih baru, library statis C/C++ juga akan disediakan. Jika ada, sebaiknya gunakan library statis, bukan kode sumber.

Sertakan file sumber ini atau library statis sebagai bagian dari proses build Anda melalui Prefab, yang mengekspos library native dan kode sumber ke project CMake atau build NDK.

  1. Ikuti petunjuk di halaman Game Jetpack Android untuk menambahkan dependensi library GameActivity ke file build.gradle game Anda.

  2. Aktifkan prefab dengan melakukan hal berikut dengan Versi Plugin Android (AGP) 4.1+:

    • Tambahkan yang berikut ke blok android dari file build.gradle modul Anda:
    buildFeatures {
        prefab true
    }
    
    • Pilih versi Prefab, dan tetapkan ke file gradle.properties:
    android.prefabVersion=2.0.0
    

    Jika Anda menggunakan versi AGP sebelumnya, ikuti dokumentasi prefab untuk petunjuk konfigurasi yang sesuai.

  3. Impor library statis C/C++ atau kode sumber C/++ ke project Anda sebagai berikut.

    Library statis

    Dalam file CMakeLists.txt project Anda, impor library statis game-activity ke dalam modul prefab game-activity_static:

    find_package(game-activity REQUIRED CONFIG)
    target_link_libraries(${PROJECT_NAME} PUBLIC log android
    game-activity::game-activity_static)
    

    Kode sumber

    Dalam file CMakeLists.txt project Anda, impor paket game-activity lalu tambahkan ke target Anda. Paket game-activity memerlukan libandroid.so, jadi jika tidak ada, Anda juga harus mengimpornya.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    

    Selain itu, sertakan file berikut ke dalam CmakeLists.txt project Anda: GameActivity.cpp, GameTextInput.cpp, dan android_native_app_glue.c.

Cara Android meluncurkan Aktivitas Anda

Sistem Android akan mengeksekusi kode dalam instance Aktivitas Anda dengan memanggil metode callback yang sesuai dengan level siklus proses aktivitas tertentu. Agar Android dapat meluncurkan aktivitas dan memulai game, Anda harus mendeklarasikan aktivitas Anda dengan atribut yang sesuai di Manifes Android. Untuk mengetahui informasi selengkapnya, lihat Pengantar Aktivitas.

Manifes Android

Setiap project aplikasi harus memiliki file AndroidManifest.xml di root set sumber project. File manifes menjelaskan informasi penting tentang aplikasi Anda ke alat build Android, sistem operasi Android, dan Google Play. Hal ini mencakup:

Menerapkan GameActivity dalam game Anda

  1. Buat atau identifikasi class Java aktivitas utama Anda (yang ditentukan dalam elemen activity di dalam file AndroidManifest.xml). Ubah class ini untuk memperluas GameActivity dari paket com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Pastikan di awal bahwa library native Anda dimuat menggunakan blok statis:

    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");
      }
      ...
    }
    
  3. Tambahkan library native Anda ke AndroidManifest.xml jika nama library Anda bukan nama default (libmain.so):

    <meta-data android:name="android.app.lib_name"
     android:value="android-game" />
    

Mengimplementasikan android_main

  1. Library android_native_app_glue adalah library kode sumber yang digunakan game Anda untuk mengelola peristiwa siklus proses GameActivity di thread terpisah untuk mencegah pemblokiran di thread utama. Saat menggunakan library, Anda mendaftarkan callback untuk menangani peristiwa siklus proses, seperti input sentuh peristiwa. Arsip GameActivity menyertakan versi library android_native_app_glue-nya sendiri, sehingga Anda tidak dapat menggunakan versi yang disertakan dalam rilis NDK. Jika game Anda menggunakan library android_native_app_glue yang disertakan dalam NDK, beralihlah ke versi GameActivity.

    Setelah Anda menambahkan kode sumber library android_native_app_glue ke project, kode tersebut akan berinteraksi dengan GameActivity. Implementasikan fungsi bernama android_main, yang dipanggil oleh library dan digunakan sebagai titik entri untuk game Anda. Hal tersebut akan melewati struktur yang disebut android_app. Hal ini mungkin berbeda untuk game dan mesin Anda. Berikut contohnya:

    #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;
    }
    
  2. Proses android_app dalam game loop utama, seperti melakukan polling dan menangani peristiwa siklus aplikasi yang ditentukan di NativeAppGlueAppCmd. Misalnya, cuplikan berikut mendaftarkan fungsi _hand_cmd_proxy sebagai pengendali NativeAppGlueAppCmd, lalu melakukan polling peristiwa siklus aplikasi, dan mengirimkannya ke pengendali terdaftar (di android_app::onAppCmd) untuk diproses:

    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();
        }
      }
    }
    
  3. Untuk membaca lebih lanjut, pelajari implementasi Endless Tunnel Contoh NDK. Perbedaan utamanya adalah bagaimana cara menangani peristiwa seperti yang ditampilkan di bagian berikutnya.

Menangani peristiwa

Agar peristiwa input dapat menjangkau aplikasi Anda, buat dan daftarkan filter peristiwa dengan android_app_set_motion_event_filter dan android_app_set_key_event_filter. Secara default, library native_app_glue hanya mengizinkan peristiwa gerakan dari input SOURCE_TOUCHSCREEN. Pastikan untuk melihat dokumen referensi dan kode implementasi android_native_app_glue untuk mengetahui detailnya.

Untuk menangani peristiwa input, dapatkan referensi android_input_buffer dengan android_app_swap_input_buffers() di game loop. Referensi tersebut berisi peristiwa gerakan dan peristiwa tombol yang terjadi sejak terakhir kali di-polling. Jumlah peristiwa yang terkandung disimpan masing-masing di motionEventsCount dan keyEventsCount.

  1. Lakukan iterasi dan tangani setiap peristiwa di game loop. Pada contoh ini, kode berikut melakukan iterasi motionEvents dan menanganinya melalui 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);
    }
    

    Lihat Contoh GitHub untuk implementasi _cookEventForPointerIndex() dan fungsi terkait.

  2. Setelah selesai, jangan lupa untuk menghapus antrean peristiwa yang baru saja Anda tangani:

    android_app_clear_motion_events(mApp);
    

Referensi lainnya

Untuk mempelajari GameActivity lebih lanjut, lihat referensi berikut:

Untuk melaporkan bug atau meminta fitur baru ke GameActivity, gunakan issue tracker GameActivity.