GameActivity   Một phần của Android Game Development Kit.

GameActivity giúp bạn chuyển trò chơi được viết bằng C hoặc C++ sang Android bằng cách đơn giản hoá quy trình sử dụng các API quan trọng.

Để tìm hiểu về một mẫu tích hợp GameActivity, hãy xem kho lưu trữ trò chơi-mẫu.

Thiết lập bản dựng

Trên Android, Activity đóng vai trò là điểm truy cập cho trò chơi, đồng thời cung cấp Window để vẽ trong đó. Nhiều trò chơi mở rộng Activity này bằng lớp Java hoặc Kotlin riêng để vượt qua các giới hạn trong NativeActivity trong khi sử dụng mã JNI để kết nối với mã trò chơi được viết bằng C hoặc C++.

GameActivity cung cấp các khả năng sau:

  • Kế thừa từ AppCompatActivity, cho phép bạn sử dụng Thành phần kiến trúc Android Jetpack.

  • Hiển thị vào SurfaceView để cho phép bạn tương tác với mọi thành phần trên giao diện người dùng Android khác.

  • Xử lý các sự kiện hoạt động trong Java. Khả năng này cho phép mọi thành phần trên giao diện người dùng Android (chẳng hạn như EditText, WebView hoặc Ad) được tích hợp vào trò chơi của bạn thông qua giao diện C.

  • Cung cấp một API C tương tự như thư viện NativeActivity và thư viện android_native_app_glue.

GameActivity được phân phối dưới dạng một Android ARchive (AAR). AAR này chứa lớp Java mà bạn sẽ sử dụng trong AndroidManifest.xml, cũng như mã nguồn C và C++ sẽ triển khai các tính năng gốc của GameActivity. Đưa các tệp nguồn này vào quy trình xây dựng thông qua Prefab để hiển thị thư viện gốc và mã nguồn cho dự án CMake hoặc bản dựng NDK của bạn.

  1. Làm theo hướng dẫn trên trang Jetpack Android Games để thêm phần phụ thuộc thư viện GameActivity vào tệp build.gradle của trò chơi.

  2. Thực hiện các thao tác sau để bật prefab với Phiên bản plugin Android (AGP) 4.1 trở lên:

    • Thêm đoạn mã sau vào khối android trong tệp build.gradle trên mô-đun của bạn:
        buildFeatures {
            prefab true
        }
    
        android.prefabVersion=2.0.0
    

    Nếu bạn sử dụng các phiên bản AGP cũ, hãy làm theo tài liệu prefab để biết hướng dẫn về cấu hình tương ứng.

  3. Trong tệp CMakeLists.txt của dự án, nhập gói game-activity và thêm gói đó vào mục tiêu của bạn (game-activity cần libandroid.so, hãy bổ sung gói này nếu hiện không có):

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  4. Thêm dòng sau vào một trong các tệp .cpp của trò chơi để triển khai GameActivity:

    #include <game-activity/GameActivity.cpp>
    
  5. Thêm dòng sau vào một trong các tệp .cpp của trò chơi để triển khai GameTextInput:

    #include <game-text-input/gametextinput.cpp>
    
  6. Biên dịch và chạy ứng dụng. Nếu bạn gặp lỗi CMake, hãy xác minh liệu AAR và tệp build.gradle đã được thiết lập đúng hay chưa. Nếu không tìm thấy tệp #include, hãy xác minh tệp cấu hình CMakeLists.txt của bạn.

  7. Tạo tệp .c và thêm tệp này vào bản dựng trong tệp CMake. Thêm dòng sau vào tệp này:

    #include <game-activity/native_app_glue/android_native_app_glue.c>
    

Cách Android khởi chạy Hoạt động của bạn

Hệ thống Android thực thi mã trong thực thể của Hoạt động khi gọi phương pháp gọi lại tương ứng với các giai đoạn cụ thể trong vòng đời hoạt động. Để Android khởi chạy hoạt động và bắt đầu trò chơi, bạn cần khai báo hoạt động với các thuộc tính thích hợp trong Tệp kê khai Android. Để biết thêm thông tin, xem phần Giới thiệu về Hoạt động.

Tệp kê khai Android

Mỗi dự án ứng dụng phải có một tệp AndroidManifest.xml tại gốc của nhóm tài nguyên dự án. Tệp kê khai mô tả thông tin thiết yếu về ứng dụng của bạn cho các công cụ xây dựng của Android, hệ điều hành Android và Google Play. Trong đó có:

Triển khai GameActivity trong trò chơi của bạn

  1. Tạo hoặc xác định lớp Java hoạt động chính (lớp được chỉ định tại thành phần activity bên trong tệp AndroidManifest.xml). Thay đổi lớp này để mở rộng GameActivity từ gói com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. Bảo đảm thư viện gốc của bạn được tải ngay từ đầu bằng cách sử dụng một khối tĩnh:

    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. Thêm thư viện gốc của bạn vào AndroidManifest.xml nếu thư viện của bạn không có tên như mặc định (libmain.so):

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

Triển khai android_main

  1. Thư viện android_native_app_glue là một thư viện mã nguồn được trò chơi dùng để quản lý các sự kiện trong vòng đời của GameActivity trong một chuỗi riêng nhằm tránh bị chặn trong chuỗi chính. Khi sử dụng thư viện, bạn đăng ký lệnh gọi lại để xử lý các sự kiện trong vòng đời, chẳng hạn như các sự kiện nhập bằng cách nhấn. Khu lưu trữ GameActivity bao gồm phiên bản riêng của thư viện android_native_app_glue, vì vậy bạn không thể sử dụng phiên bản có trong bản phát hành NDK. Nếu trò chơi của bạn đang sử dụng thư viện android_native_app_glue có trong NDK, hãy chuyển sang phiên bản GameActivity.

    Sau khi bạn thêm mã nguồn thư viện android_native_app_glue vào dự án, mã này sẽ tương tác với GameActivity. Triển khai một hàm có tên là android_main. Hàm này do thư viện gọi và được dùng làm điểm truy cập cho trò chơi của bạn. Hàm này nhận được một cấu trúc được gọi là android_app. Điều này có thể khác với trò chơi và công cụ của bạn. Sau đây là một ví dụ:

    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. Xử lý android_app trong vòng lặp trò chơi chính và kiểm tra vòng về các sự kiện. Ví dụ:

    void NativeEngine::GameLoop() {
      mApp->userData = this;
      mApp->onAppCmd = _handle_cmd_proxy;
      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) {
                source->process(mApp, source);
            }
            if (mApp->destroyRequested) {
                return;
            }
        }
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  3. Để biết thêm thông tin, hãy nghiên cứu cách triển khai mẫu NDK Endless Tunnel. Điểm khác biệt chính sẽ là cách xử lý các sự kiện như được trình bày trong phần tiếp theo.

Xử lý sự kiện

Để xử lý các sự kiện nhập, đọc các mảng motionEvents, keyUpEventskeyDownEvents trong vòng lặp trò chơi. Các mảng này chứa những sự kiện đã xảy ra kể từ lần xoá mảng lần gần đây nhất. Số lượng sự kiện trong đó được lưu trữ lần lượt tại motionEventsCount, keyUpEventsCountkeyDownEventsCount.

  1. Lặp lại và xử lý từng sự kiện trong vòng lặp trò chơi. Trong ví dụ này, mã sau đây sẽ lặp lại motionEvents và xử lý các sự kiện đó qua handle_event:

    if (inputBuffer->motionEventsCount != 0) {
        for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) {
            GameActivityMotionEvent* motionEvent = &inputBuffer->motionEvents[i];
    
            if (motionEvent->pointerCount > 0) {
                int action = motionEvent->action;
                int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
                int ptrIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
    
                if (ptrIndex < motionEvent->pointerCount) {
                    struct CookedEvent ev;
                    memset(&ev, 0, sizeof(ev));
    
                    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
                        actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
                            ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                    } else if (actionMasked == AMOTION_EVENT_ACTION_UP ||
                            actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
                            ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                    } else {
                            ev.type = COOKED_EVENT_TYPE_POINTER_MOVE;
                    }
    
                    ev.motionPointerId = motionEvent->pointers[ptrIndex].id;
                    ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN;
                    ev.motionX = GameActivityPointerAxes_getX(&motionEvent->pointers[ptrIndex]);
                    ev.motionY = GameActivityPointerAxes_getY(&motionEvent->pointers[ptrIndex]);
    
                    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();
                    }
    
                    _cooked_event_callback(&ev);
                }
            }
        }
        android_app_clear_motion_events(inputBuffer);
    }
    
    

    Xem mẫu GitHub để biết cách triển khai _cooked_event_callback().

  2. Khi thực hiện xong, nhớ xoá hàng đợi các sự kiện mà bạn vừa xử lý:

    android_app_clear_motion_events(mApp);
    

Tệp đối chiếu

Để tìm hiểu thêm về GameActivity, hãy xem các nội dung sau:

Để báo cáo lỗi hoặc yêu cầu tính năng mới cho GameActivity, hãy sử dụng công cụ theo dõi lỗi của GameActivity.