بدء استخدام GameActivity جزء من Android Game Development Kit
يوضّح هذا الدليل كيفية إعداد ودمج
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 Architecture.يتم عرضها في
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++ في التطبيق. إذا كنت تستخدم الإصدار GameActivity
1.2.2 أو إصدارًا أحدث، سيتم أيضًا توفير مكتبة C/C++ الثابتة. ننصحك باستخدام المكتبة الثابتة بدلاً من رمز المصدر كلما كان ذلك ممكنًا.
أدرِج ملفات المصدر هذه أو المكتبة الثابتة كجزء من عملية الإنشاء من خلال Prefab
، الذي يعرض المكتبات الأصلية ورمز المصدر إلى مشروع CMake أو إنشاء NDK.
اتّبِع التعليمات الواردة في صفحة ألعاب Android في Jetpack لإضافة التبعية لمكتبة
GameActivity
إلى ملفbuild.gradle
الخاص بلعبتك.فعِّل العنصر الجاهز من خلال تنفيذ ما يلي باستخدام الإصدار 4.1 من المكوّن الإضافي لنظام Android المتوافق مع Gradle أو الإصدارات الأحدث:
- أضِف ما يلي إلى قسم
android
في ملفbuild.gradle
الخاص بالوحدة:
buildFeatures { prefab true }
- اختَر إصدارًا من Prefab،
واضبطه على ملف
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 من بدء نشاطك وتشغيل لعبتك، عليك تعريف نشاطك باستخدام السمات المناسبة في ملف AndroidManifest. لمزيد من المعلومات، يُرجى الاطّلاع على مقدمة عن الأنشطة.
ملف بيان 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_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(); } } }
للحصول على معلومات إضافية، يمكنك الاطّلاع على مثال النفق اللانهائي في 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.