بدء استخدام GameActivity جزء من مجموعة أدوات تطوير ألعاب Android.
يوضِّح هذا الدليل كيفية إعداد GameActivity
ودمجها والتعامل مع الأحداث في لعبة Android.
يساعدك GameActivity
في توفير لعبة C أو C++ على Android من خلال تبسيط عملية استخدام واجهات برمجة التطبيقات المهمة.
كان NativeActivity
في السابق
فصل الألعاب الموصى به. يحلّ محلّها GameActivity
باعتباره الفئة المقترَحة للألعاب، وهو متوافق مع الأنظمة القديمة المستوى 19 من واجهة برمجة التطبيقات.
للاطّلاع على نموذج يدمج GameActivity، يُرجى الاطّلاع على مستودع نماذج الألعاب.
قبل البدء
اطّلِع على إصدارات GameActivity
للحصول على عملية توزيع.
إعداد الإصدار
على أجهزة Android، تُستخدم Activity
كنقطة دخول للعبتك، وتتيح أيضًا استخدام رمز Window
للظهور في اللعبة. وتستخدم العديد من الألعاب "Activity
" باستخدام فئتها الخاصة بلغة Java أو Kotlin لإلغاء القيود في NativeActivity
عند استخدام الرمز JNI
للربط بين رمز اللعبة بلغة C أو C++.
توفّر GameActivity
الإمكانات التالية:
يمكن اكتسابه من
AppCompatActivity
، ما يتيح لك استخدام مكونات Android Jetpack البنية.يتم عرضه في
SurfaceView
يسمح لك بالتفاعل مع أي عنصر آخر في واجهة المستخدم على Android.معالجة أحداث نشاط Java. يسمح ذلك بدمج أي عنصر من عناصر واجهة مستخدم Android (مثل
EditText
أوWebView
أوAd
) في لعبتك عبر واجهة C.تقدم واجهة برمجة تطبيقات C API مشابهة لـ
NativeActivity
، ومكتبةandroid_native_app_glue
.
يتم توزيع GameActivity
باعتباره أرشيف Android
(AAR). يحتوي AAR هذا على فئة Java التي
تستخدمها في
AndroidManifest.xml
، بالإضافة إلى رمز المصدر C
وC++ الذي يربط جانب Java في GameActivity
بتنفيذ C/C++ للتطبيق. إذا كنت تستخدم الإصدار 1.2.2 من "GameActivity
" أو إصدار أحدث، يتم أيضًا توفير المكتبة الثابتة C/C++. ننصحك باستخدام المكتبة الثابتة بدلاً من رمز المصدر، كلما أمكن ذلك.
يمكنك تضمين ملفات المصدر هذه أو المكتبة الثابتة كجزء من عملية الإنشاء من خلال Prefab
، التي تعرض المكتبات الأصلية ورمز المصدر إلى مشروع CMake أو إصدار NDK.
اتّبِع التعليمات الواردة في صفحة Jetpack Android Games لإضافة مكتبة "
GameActivity
" الاعتمادية إلى ملفbuild.gradle
الخاص بلعبتك.مكِّن الإعداد المسبق عن طريق تنفيذ ما يلي باستخدام إصدار مكون Android الإضافي (AGP) 4.1 والإصدارات الأحدث:
- أضف ما يلي إلى الجزء
android
من ملفbuild.gradle
في وحدتك:
buildFeatures { prefab true }
- اختر نسخة سابقة،
وعيّنها على ملف
gradle.properties
:
android.prefabVersion=2.0.0
إذا كنت تستخدم إصدارات AGP سابقًا، اتّبِع مستندات الإعدادات المسبقة للاطّلاع على تعليمات الإعداد ذات الصلة.
- أضف ما يلي إلى الجزء
قم باستيراد مكتبة C/C++ الثابتة أو رمز المصدر C/++ إلى مشروعك على النحو التالي.
مكتبة ثابتة
في ملف
CMakeLists.txt
الخاص بمشروعك، استورِد المكتبة الثابتةgame-activity
إلى وحدة الإعدادات التمهيدية "game-activity_static
":find_package(game-activity REQUIRED CONFIG) target_link_libraries(${PROJECT_NAME} PUBLIC log android game-activity::game-activity_static)
رمز مصدر
في ملف
CMakeLists.txt
لمشروعك، قم باستيراد حزمةgame-activity
وإضافتها إلى الهدف. تتطلب حزمةgame-activity
libandroid.so
، لذلك إذا كانت مفقودة، يجب أيضًا استيرادها.find_package(game-activity REQUIRED CONFIG) ... target_link_libraries(... android game-activity::game-activity)
عليك أيضًا تضمين الملفات التالية في
CmakeLists.txt
الخاص بمشروعك:GameActivity.cpp
وGameTextInput.cpp
وandroid_native_app_glue.c
.
كيفية إطلاق Android لنشاطك
ينفذ نظام Android التعليمات البرمجية في مثيل نشاطك عن طريق استدعاء طرق معاودة الاتصال التي تتوافق مع مراحل معينة من دورة حياة النشاط. ليتمكّن نظام التشغيل Android من إطلاق نشاطك وبدء تشغيل لعبتك، عليك الإعلان عن نشاطك باستخدام السمات المناسبة في بيان Android. لمزيد من المعلومات، راجع مقدمة حول الأنشطة.
بيان Android
يجب أن يحتوي كل مشروع تطبيق على ملف AndroidManifest.xml في جذر مجموعة مصدر المشروع. يصف ملف البيان المعلومات الأساسية عن تطبيقك في أدوات إصدار Android ونظام التشغيل Android وGoogle Play. يشمل ذلك ما يلي:
اسم الحزمة ورقم تعريف التطبيق للتعرّف على لعبتك بشكل فريد على Google Play
مكونات التطبيق مثل الأنشطة والخدمات وأجهزة استقبال البث وموفري المحتوى.
الأذونات للوصول إلى الأجزاء المحمية من النظام أو التطبيقات الأخرى.
التوافق مع الأجهزة لتحديد متطلبات الأجهزة والبرامج للعبتك
اسم المكتبة الأصلي للسمتَين
GameActivity
وNativeActivity
(القيمة التلقائية هي libmain.so).
تنفيذ نشاط GameActivity في لعبتك
أنشئ فئة Java للنشاط الرئيسي أو حدِّدها (القيمة المحددة في العنصر
activity
داخل ملفAndroidManifest.xml
). غيِّر هذه الفئة لإطالةGameActivity
من حزمةcom.google.androidgamesdk
:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }
احرِص على تحميل مكتبتك الأصلية في البداية باستخدام وحدة ثابتة:
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"); } ... }
أضِف مكتبتك الأصلية إلى
AndroidManifest.xml
إذا لم يكن اسم مكتبتك هو الاسم التلقائي (libmain.so
):<meta-data android:name="android.app.lib_name" android:value="android-game" />
تنفيذ android_main
إنّ مكتبة
android_native_app_glue
هي مكتبة رموز مصدر تستخدمها لعبتك لإدارة أحداث مراحل نشاطGameActivity
في سلسلة محادثات منفصلة من أجل منع الحظر في سلسلة التعليمات الرئيسية. عند استخدام المكتبة، يمكنك تسجيل معاودة الاتصال لمعالجة أحداث مراحل النشاط، مثل أحداث الإدخال باللمس. يتضمن أرشيفGameActivity
نسخته الخاصة من مكتبةandroid_native_app_glue
، ولذلك لا يمكنك استخدام النسخة المضمَّنة في إصدارات NDK. إذا كانت ألعابك تستخدم مكتبةandroid_native_app_glue
المضمَّنة في NDK، يمكنك التبديل إلى إصدارGameActivity
.بعد إضافة رمز المصدر الخاص بمكتبة
android_native_app_glue
إلى مشروعك، سيواجهGameActivity
. نفِّذ دالة تُسمىandroid_main
، والتي تطلبها المكتبة وتستخدمها كنقطة دخول للعبتك. تم تجاوز هيكل يسمىandroid_app
. قد يختلف هذا المبلغ حسب اللعبة والمحرّك. إليك مثال على ذلك:#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; }
عالج
android_app
في حلقة الألعاب الرئيسية، مثل الاستطلاعات ومعالجة أحداث دورة التطبيق المحددة في NativeAppGlueAppCmd. على سبيل المثال، يسجِّل المقتطف التالي الدالة_hand_cmd_proxy
كمعالجNativeAppGlueAppCmd
، ثم يستقيل أحداث دورة التطبيق ويرسلها إلى المعالج المسجَّل(فيandroid_app::onAppCmd
) لمعالجته: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(); } } }
لمزيد من القراءة، ادرس تنفيذ مثال NDK Endless Tunnel. سيكون الاختلاف الرئيسي هو كيفية التعامل مع الأحداث كما هو موضح في القسم التالي.
التعامل مع الأحداث
لتفعيل أحداث الإدخال من أجل الوصول إلى تطبيقك، عليك إنشاء فلاتر الأحداث
وتسجيلها باستخدام android_app_set_motion_event_filter
وandroid_app_set_key_event_filter
.
وفقًا للإعدادات التلقائية، تسمح مكتبة native_app_glue
باستخدام أحداث الحركة من الإدخال
SOURCE_TOUCHSCREEN فقط. للمزيد من التفاصيل، يمكنك مراجعة المستند المرجعي ورمز التطبيق android_native_app_glue
.
لمعالجة أحداث الإدخال، عليك إضافة إشارة إلى android_input_buffer
مع إضافة
android_app_swap_input_buffers()
في حلقة الألعاب. ويحتوي ذلك على أحداث الحركة والأحداث الرئيسية التي حدثت منذ آخر مرة تم فيها استطلاع الرأي. ويتم تخزين عدد الأحداث المضمَّنة في motionEventsCount
وkeyEventsCount
على التوالي.
كرِّر جميع الأحداث ونظِّمها في حلقة الألعاب. في هذا المثال، يكرّر الرمز التالي
motionEvents
ويعالجه من خلالhandle_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); }
يمكنك الاطّلاع على نموذج GitHub لتنفيذ
_cookEventForPointerIndex()
والوظائف الأخرى ذات الصلة.عند الانتهاء، تذكر محو قائمة الانتظار بالأحداث التي تعاملت معها للتو:
android_app_clear_motion_events(mApp);
مصادر إضافية
لمزيد من المعلومات عن "GameActivity
"، يُرجى الاطّلاع على ما يلي:
- ملاحظات إصدار GameActivity وAGDK
- استخدام GameTextInput في GameActivity
- دليل نقل بيانات NativeActivity
- المستندات المرجعية للعبة GameActivity
- تنفيذ GameActivity:
للإبلاغ عن الأخطاء أو لطلب ميزات جديدة في GameActivity، يمكنك استخدام أداة تتبّع مشاكل GameActivity.