بدء استخدام 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 تشبه مكتبة
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 لإضافة إضافة مكتبة "
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 (فئة 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(); } } }
لمزيد من القراءة، يمكنك دراسة تنفيذ مثال Inless Tunnel NDK. سيكون الاختلاف الرئيسي هو كيفية التعامل مع الأحداث كما هو موضّح في القسم التالي.
التعامل مع الأحداث
لتفعيل أحداث الإدخال للوصول إلى تطبيقك، يمكنك إنشاء فلاتر الأحداث وتسجيلها باستخدام 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.