Làm quen với GameActivity Một phần của Android Game Development Kit.
Hướng dẫn này mô tả cách thiết lập, tích hợp
GameActivity và xử lý các sự kiện trong trò chơi
Android.
GameActivity giúp bạn chuyển trò chơi được viết bằng ngôn 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.
Trước đây, NativeActivity là
là lớp được đề xuất cho trò chơi. GameActivity thay thế lớp này làm lớp được đề xuất cho trò chơi và tương thích ngược với API cấp 19.
Để xem các mẫu tích hợp GameActivity, hãy truy cập kho lưu trữ trò chơi-mẫu.
Trước khi bắt đầu
Xem bản phát hành GameActivity để nhận bản phân phối.
Thiết lập bản dựng
Trên Android, Activity đóng vai trò là điểm bắt đầu của 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 để làm cầu nối cho mã trò chơi được viết bằng C hoặc C++.
GameActivity có các tính 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,WebViewhoặcAd) đượ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ư
NativeActivityvà thư việnandroid_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ử dụng trong AndroidManifest.xml cũng như mã nguồn C và C++ kết nối phía Java của GameActivity với quá trình triển khai C/C++ của ứng dụng. Nếu bạn đang sử dụng GameActivity 1.2.2 trở lên, thì thư viện tĩnh C/C++ cũng được cung cấp. Nếu có thể, bạn nên sử dụng
thư viện tĩnh thay cho mã nguồn.
Đưa thư viện tĩnh hoặc 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.
Làm theo hướng dẫn trên trang Jetpack Android Games để thêm phần phụ thuộc thư viện
GameActivityvào tệpbuild.gradlecủa trò chơi.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 
androidtrong tệpbuild.gradletrên mô-đun của bạn: 
buildFeatures { prefab true }- Chọn một phiên bản Prefab và đặt vào tệp 
gradle.properties: 
android.prefabVersion=2.0.0Nếu bạn sử dụng các phiên bản AGP cũ, hãy làm theo tài liệu về prefab để biết hướng dẫn về cấu hình tương ứng.
- Thêm đoạn mã sau vào khối 
 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.txtcủa dự án, hãy nhập thư viện tĩnhgame-activityvào mô-đun prefabgame-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.txtcủa dự án, hãy nhập góigame-activityvà thêm gói đó vào mục tiêu của bạn. Góigame-activitycầnlibandroid.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)Ngoài ra, hãy đưa các tệp sau vào
CmakeLists.txtcủa dự án:GameActivity.cpp,GameTextInput.cppvà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 thức 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, hãy 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ó:
Tên gói và ID ứng dụng để nhận dạng rõ ràng trò chơi của bạn trên Google Play.
Thành phần của ứng dụng như hoạt động, dịch vụ, bộ nhận tín hiệu truyền tin và nhà cung cấp nội dung.
Quyền để truy cập vào các phần được bảo vệ của hệ thống hoặc ứng dụng khác.
Khả năng tương thích của thiết bị để chỉ định yêu cầu về phần cứng và phần mềm cho trò chơi của bạn.
Tên thư viện gốc cho
GameActivityvàNativeActivity(mặc định là libmain.so).
Triển khai GameActivity trong trò chơi của bạn
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
activitybên trong tệpAndroidManifest.xml). Thay đổi lớp này để mở rộngGameActivitytừ góicom.google.androidgamesdk:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }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"); } ... }Thêm thư viện gốc của bạn vào
AndroidManifest.xmlnế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
Thư viện
android_native_app_gluelà một thư viện mã nguồn mà trò chơi của bạn dùng để quản lý các sự kiện trong vòng đời củaGameActivitytrong một luồng riêng nhằm tránh bị chặn trong luồng 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 chạm). Kho lưu trữGameActivitybao gồm phiên bản riêng của thư việnandroid_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ệnandroid_native_app_gluecó trong NDK, hãy chuyển sang phiên bảnGameActivity.Sau khi bạn thêm mã nguồn thư viện
android_native_app_gluevào dự án, mã này sẽ tương tác vớiGameActivity. 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 bắt đầu 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. Ví dụ:#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; }Xử lý
android_apptrong vòng lặp chính của trò chơi, chẳng hạn như thăm dò và xử lý các sự kiện trong chu kỳ ứng dụng được xác định trong NativeAppGlueAppCmd. Ví dụ: đoạn mã sau đây đăng ký hàm_hand_cmd_proxylà trình xử lýNativeAppGlueAppCmd, sau đó thăm dò sự kiện chu kỳ ứng dụng và gửi các sự kiện đó đến trình xử lý đã đăng ký (trongandroid_app::onAppCmd) để xử lý: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_pollOnce(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(); } } }Để 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
Để cho phép các sự kiện đầu vào truy cập ứng dụng, hãy tạo và đăng ký bộ lọc sự kiện bằng android_app_set_motion_event_filter và android_app_set_key_event_filter.
Theo mặc định, thư viện native_app_glue chỉ cho phép các sự kiện chuyển động từ phương thức nhập
SOURCE_TOUCHSCREEN. Vui lòng xem tài liệu tham khảo và mã triển khai android_native_app_glue để biết thêm thông tin chi tiết.
Để xử lý các sự kiện đầu vào, hãy tham chiếu đến android_input_buffer bằng
android_app_swap_input_buffers() trong vòng lặp trò chơi của bạn. Các tham số này chứa sự kiện chuyển động và sự kiện chính đã xảy ra kể từ lần gần đây nhất sự kiện đó được thăm dò ý kiến. Số lượng sự kiện trong đó được lưu trữ lần lượt tại motionEventsCount và keyEventsCount.
Lặp và xử lý từng sự kiện trong vòng lặp trò chơi. Trong ví dụ này, đoạn mã sau đây sẽ lặp
motionEventsvà xử lý các sự kiện đó quahandle_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); }Hãy xem mẫu GitHub để biết cách triển khai
_cookEventForPointerIndex()và các hàm có liên quan khác.Nhớ xoá các sự kiện mà bạn vừa xử lý sau khi thực hiện xong:
android_app_clear_motion_events(mApp);
Tài nguyên khác
Để tìm hiểu thêm về GameActivity, hãy xem các nội dung sau:
- Ghi chú phát hành của GameActivity và AGDK.
 - Sử dụng GameTextInput trong GameActivity.
 - Hướng dẫn di chuyển NativeActivity.
 - Tài liệu tham khảo về GameActivity.
 - Triển khai GameActivity.
 
Để 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.