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ặ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ư thư viện
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ẽ 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.
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 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
Trong tệp
CMakeLists.txt
của dự án, nhập góigame-activity
và thêm gói đó vào mục tiêu của bạn (game-activity cầnlibandroid.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)
Trong một tệp
.cpp
hiện có trong trò chơi của bạn hoặc trong một tệp.cpp
mới, hãy thêm tệp sau đây để bao gồm tệpGameActivity
,GameTextInput
và quảng cáo gốc đồng hành triển khai keo:#include <game-activity/GameActivity.cpp> #include <game-text-input/gametextinput.cpp> extern "C" { #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ó:
Tên gói và mã nhận dạng ứng dụng để xác định riê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ụ, broadcast receiver 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.
Tính 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 được trò chơi dùng để quản lý các sự kiện trong vòng đời củaGameActivity
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ệ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 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ụ:#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 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(); } } }
Để 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
, keyUpEvents
và keyDownEvents
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
, keyUpEventsCount
và keyDownEventsCount
.
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 đó quahandle_event
:auto 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) { 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()
.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.