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
,WebView
hoặ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ư
NativeActivity
và 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
GameActivity
vào tệpbuild.gradle
củ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
android
trong tệpbuild.gradle
trê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.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 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.txt
của dự án, hãy nhập thư viện tĩnhgame-activity
và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.txt
của dự án, hãy nhập góigame-activity
và thêm gói đó vào mục tiêu của bạn. Góigame-activity
cầ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.txt
của dự án:GameActivity.cpp
,GameTextInput.cpp
và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
GameActivity
và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
activity
bên trong tệpAndroidManifest.xml
). Thay đổi lớp này để mở rộngGameActivity
từ 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.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
Thư viện
android_native_app_glue
là 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ủaGameActivity
trong 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ư nhập bằng cách chạm) các sự kiện. Kho lưu trữGameActivity
bao 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_glue
có 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_glue
và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_app
trong 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_proxy
là 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_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(); } } }
Để đọc thêm, hãy nghiên cứu cách triển khai Endless Tunnel Ví dụ về NDK. Đ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
motionEvents
và 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); }
Xem Mẫu GitHub để triển khai
_cookEventForPointerIndex()
và các các hàm liên quan.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.