Di chuyển từ NativeActivity Một phần của Android Game Development Kit.

Trang này mô tả cách di chuyển từ NativeActivity sang GameActivity trong dự án trò chơi Android của bạn.

GameActivity dựa trên NativeActivity trong khung Android với các tính năng nâng cao mới:

  • Hỗ trợ Fragment từ Jetpack.
  • Thêm tuỳ chọn hỗ trợ TextInput để hỗ trợ tích hợp bàn phím mềm.
  • Xử lý các sự kiện chạm và sự kiện chính trong lớp Java GameActivity thay vì giao diện NativeActivity onInputEvent.

Trước khi di chuyển, bạn nên đọc hướng dẫn bắt đầu sử dụng. Hướng dẫn này mô tả cách thiết lập và tích hợp GameActivity vào dự án của bạn.

Cập nhật tập lệnh bản dựng Java

GameActivity được phân phối dưới dạng một thư viện Jetpack. Hãy nhớ áp dụng tập lệnh Gradle để cập nhật các bước như mô tả trong hướng dẫn bắt đầu sử dụng:

  1. Bật thư viện Jetpack trong tệp gradle.properties của dự án:

    android.useAndroidX=true
    
  2. Nếu muốn, bạn có thể chỉ định một phiên bản Prefab, trong cùng một tệp gradle.properties, ví dụ:

    android.prefabVersion=2.0.0
    
  3. Bật tính năng Prefab trong tệp build.gradle của ứng dụng:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. Thêm phần phụ thuộc GameActivity vào ứng dụng của bạn:

    1. Thêm thư viện coregames-activity.
    2. Nếu hiện tại, cấp độ API tối thiểu được hỗ trợ của bạn là dưới 16, hãy cập nhật lên ít nhất là 16.
    3. Cập nhật phiên bản SDK đã biên dịch lên phiên bản mà thư viện games-activity yêu cầu. Jetpack thường yêu cầu phiên bản SDK mới nhất tại thời điểm tạo bản phát hành.

    Tệp build.gradle mới cập nhật của bạn có thể trông như sau:

    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'
    }
    

Cập nhật mã Kotlin hoặc Java

NativeActivity có thể được dùng làm hoạt động khởi động và tạo ứng dụng toàn màn hình. Hiện tại, GameActivity không thể được sử dụng làm hoạt động khởi động. Các ứng dụng phải lấy một lớp từ GameActivity và sử dụng lớp đó làm hoạt động khởi động. Bạn cũng phải thực hiện các thay đổi khác đối với cấu hình để tạo ứng dụng toàn màn hình.

Các bước sau đây giả định ứng dụng của bạn sử dụng NativeActivity làm hoạt động khởi động. Nếu trường hợp này không xảy ra, bạn có thể bỏ qua hầu hết các trường hợp.

  1. Tạo một tệp trong Kotlin hoặc Java để lưu trữ hoạt động khởi động mới. Ví dụ: mã sau đây tạo MainActivity làm hoạt động khởi động và tải thư viện gốc chính của ứng dụng, 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. Tạo giao diện toàn ứng dụng ở chế độ toàn màn hình trong tệp 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. Áp dụng giao diện cho ứng dụng trong tệp AndroidManifest.xml:

    <application  android:theme=”@style/Application.Fullscreen”>
         <!-- other configurations not listed here. -->
    </application>
    

    Để nắm được hướng dẫn chi tiết về chế độ toàn màn hình, hãy tham khảo hướng dẫn về chế độ sống động và ví dụ về cách triển khai trong kho lưu trữ mẫu trò chơi.

Hướng dẫn di chuyển này không thay đổi tên thư viện gốc. Nếu bạn thay đổi tên đó, hãy đảm bảo tên của các thư viện gốc nhất quán ở ba vị trí sau:

  • Mã Kotlin hoặc Java:

    System.loadLibrary(“AndroidGame”)
    
  • AndroidManifest.xml:

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • Bên trong tệp tập lệnh bản dựng C/C++, ví dụ: CMakeLists.txt

    add_library(AndroidGame ...)
    

C/C++ tạo bản cập nhật tập lệnh

Các hướng dẫn trong phần này sử dụng cmake làm ví dụ. Nếu ứng dụng của bạn sử dụng ndk-build, bạn cần liên kết các ứng dụng đó với các lệnh tương đương được mô tả trong trang tài liệu về ndk-build.

Việc triển khai C/C++ của GameActivity đã cung cấp một bản phát hành mã nguồn. Đối với phiên bản 1.2.2 trở lên, hệ thống sẽ cung cấp một bản phát hành thư viện tĩnh. Thư viện tĩnh là loại bản phát hành được đề xuất.

Bản phát hành này được đóng gói trong AAR cùng với tiện ích prefab. Mã gốc bao gồm các nguồn C/C++ của GameActivity và mã native_app_glue. Các mã này cần được xây dựng cùng với mã C/C++ của ứng dụng.

Các ứng dụng NativeActivity đã sử dụng mã native_app_glue được vận chuyển bên trong NDK. Bạn phải thay thế điều này bằng phiên bản native_app_glue của GameActivity. Ngoài ra, tất cả các bước cmake có trong tài liệu hướng dẫn bắt đầu này đều được áp dụng:

  • Nhập thư viện tĩnh C/C++ hoặc mã nguồn C/++ vào dự án của bạn theo cách dưới đây.

    Thư viện tĩnh

    Trong tệp CMakeLists.txt của dự án, hãy nhập thư viện tĩnh game-activity vào mô-đun prefab game-activity_static:

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

    Mã nguồn

    Trong tệp CMakeLists.txt của dự án, hãy nhập gói game-activity và thêm gói đó vào mục tiêu của bạn. Gói game-activity yêu cầu libandroid.so, vì vậy nếu còn thiếu, bạn cũng phải nhập gói này.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  • Xóa tất cả thông tin tham chiếu đến mã native_app_glue của NDK, chẳng hạn như:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • Nếu bạn đang dùng bản phát hành mã nguồn, hãy thêm các tệp nguồn GameActivity. Nếu không, hãy bỏ qua bước này.

    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)
    

Khắc phục lỗi UnsatisfiedLinkError

Nếu bạn gặp UnsatsifiedLinkError cho hàm com.google.androidgamesdk.GameActivity.initializeNativeCode(), hãy thêm mã này vào tệp CMakeLists.txt:

set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")

Cập nhật mã nguồn C/C++

Làm theo các bước sau để thay thế các tệp tham chiếu NativeActivity trong ứng dụng của bạn bằng GameActivity:

  • Sử dụng native_app_glue được phát hành với GameActivity. Tìm kiếm và thay thế mọi hoạt động sử dụng android_native_app_glue.h thành:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • Đặt cả bộ lọc sự kiện chuyển động lẫn bộ lọc sự kiện chính thành giá trị NULL để ứng dụng của bạn có thể nhận các sự kiện đầu vào từ mọi thiết bị đầu vào. Thông thường, bạn sẽ phải thực hiện việc này bên trong hàm 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.
    }
    
  • Xóa mã liên quan đến AInputEvent và thay thế mã đó bằng cách triển khai InputBuffer của GameActivity:

    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;
    }
    
  • Xem lại và cập nhật logic được liên kết với AInputEvent của NativeActivity. Như đã thấy trong bước trước, quá trình xử lý InputBuffer của GameActivity nằm ngoài vòng lặp ALooper_pollAll().

  • Thay thế mức sử dụng android_app::activity->clazz bằng android_app:: activity->javaGameActivity. GameActivity đổi tên bản sao Java GameActivity.

Các bước bổ sung

Các bước trước có tính đến cả chức năng của NativeActivity, nhưng GameActivity có các tính năng bổ sung mà bạn có thể muốn sử dụng:

Bạn nên khám phá và sử dụng những tính năng này cho phù hợp với trò chơi của mình.

Nếu bạn có thắc mắc hoặc đề xuất về GameActivity hoặc thư viện AGDK khác, hãy tạo lỗi để báo cho chúng tôi.