نقل البيانات من NativeActivity جزء من حزمة تطوير ألعاب Android.

تصف هذه الصفحة كيفية النقل من NativeActivity إلى GameActivity في مشروع لعبة Android الخاص بك

تستند ميزة "GameActivity" إلى NativeActivity من نظام Android. مع تحسينات وميزات جديدة:

  • يتوافق مع "Fragment" من Jetpack.
  • تعمل هذه السياسة على إضافة دعم "TextInput" لتسهيل دمج لوحة المفاتيح الافتراضية.
  • يعالج هذا الإجراء أحداث اللمس والأحداث الرئيسية في فئة Java التي تتضمن GameActivity بدلاً من واجهة onInputEvent NativeActivity.

قبل إجراء عملية النقل، ننصحك بقراءة دليل البدء، والذي يصف كيف لإعداد GameActivity ودمجها في مشروعك.

تحديثات النص البرمجي لإصدار Java

تم توزيع GameActivity باعتباره مكتبة Jetpack. تأكَّد من تطبيق الخطوات الموضّحة في ما يتعلّق بتعديل نص Gradle البرمجي. في دليل البدء:

  1. تفعيل مكتبة Jetpack في ملف gradle.properties الخاص بمشروعك:

    android.useAndroidX=true
    
  2. اختياريًا، حدِّد إصدارًا مُسبقًا في ملف gradle.properties نفسه. على سبيل المثال:

    android.prefabVersion=2.0.0
    
  3. تفعيل ميزة Prefab في ملف build.gradle لتطبيقك:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. أضِف تبعية GameActivity إلى تطبيقك:

    1. أضِف المكتبات core وgames-activity.
    2. إذا كان الحد الأدنى الحالي لمستوى واجهة برمجة التطبيقات المتوافق أقل من 16، عليك تحديثه. إلى 16 على الأقل.
    3. حدِّث إصدار حزمة تطوير البرامج (SDK) المجمّع إلى الإصدار الذي تستخدمه السياسة games-activity المطلوبة للمكتبة. تتطلب عادةً Jetpack أحدث إصدار من حزمة SDK وقت إنشاء الإصدار.

    قد يظهر ملف build.gradle المعدَّل على النحو التالي:

    android {
        compiledSdkVersion 33
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.9.0'
        implementation 'androidx.games:games-activity:1.2.2'
    }
    

تحديثات رمز Kotlin أو Java

يمكن استخدام "NativeActivity" كنشاط بدء تشغيل وإنشاء وضع ملء الشاشة التطبيق. في الوقت الحالي، لا يمكن استخدام GameActivity كشركة ناشئة الأخرى. يجب أن تحصل التطبيقات على فئة من GameActivity وتستخدمها على أنّها نشاط بدء التشغيل. ويجب عليك أيضًا إجراء تغييرات تهيئة إضافية على إنشاء تطبيق بملء الشاشة.

تفترض الخطوات التالية أنّ تطبيقك يستخدم "NativeActivity" كشركة ناشئة. الأخرى. وإذا لم يكن الأمر كذلك، يمكنك تخطي معظمها.

  1. أنشِئ ملف Kotlin أو Java لاستضافة نشاط بدء التشغيل الجديد. على سبيل المثال: ينشئ الرمز التالي MainActivity كنشاط بدء التشغيل تحمّل المكتبة الأصلية للتطبيق، libAndroidGame.so:

    Kotlin

    class MainActivity : GameActivity() {
       override fun onResume() {
           super.onResume()
           // Use the function recommended from the following page:
           // https://d.android.com/training/system-ui/immersive
           hideSystemBars()
       }
       companion object {
           init {
               System.loadLibrary("AndroidGame")
           }
       }
    }
    

    Java

      public class MainActivity extends GameActivity {
          protected void onResume() {
              super.onResume();
              // Use the function recommended from
              // https://d.android.com/training/system-ui/immersive
              hideSystemBars();
          }
          static {
              System.loadLibrary("AndroidGame");
          }
      }
    
  2. إنشاء مظهر تطبيق بملء الشاشة في ملف res\values\themes.xml:

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar">
            <item name="android:windowFullscreen">true</item>
            <item name="android:windowContentOverlay">@null</item>"
        </style>
    </resources>
    
  3. يمكنك تطبيق المظهر على التطبيق في ملف AndroidManifest.xml:

    <application  android:theme=”@style/Application.Fullscreen”>
         <!-- other configurations not listed here. -->
    </application>
    

    للحصول على إرشادات تفصيلية حول وضع ملء الشاشة، يمكنك الاطّلاع على دليل شامل ومثال على التنفيذ في مستودع نماذج الألعاب

لا يؤدي دليل نقل البيانات هذا إلى تغيير اسم المكتبة الأصلية. إذا غيّرت تأكد من أن أسماء المكتبة الأصلية متسقة في ما يلي المواقع:

  • رمز Kotlin أو Java:

    System.loadLibrary(“AndroidGame”)
    
  • AndroidManifest.xml:

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • داخل ملف النص البرمجي للإصدار C/C++ ، على سبيل المثال CMakeLists.txt:

    add_library(AndroidGame ...)
    

تحديثات النص البرمجي لإصدار C/C++

تستخدم التعليمات الواردة في هذا القسم cmake كمثال. إذا كان طلبك تستخدم ndk-build، عليك ربطها بالأوامر المكافئة الموضحة في صفحة مستندات ndk-build.

تم توفير إصدار رمز المصدر من خلال تنفيذ لغة C/C++ في GameActivity. بالنسبة إلى الإصدار 1.2.2 والإصدارات الأحدث، يتم توفير إصدار مكتبة ثابتة. الثابت هو نوع الإصدار الموصى به.

تتم تعبئة الإصدار داخل الاقتراحات المطبّقة تلقائيًا مع prefab الأخرى. يتضمن الرمز الأصلي مصدر C/C++ من GameActivity، بالإضافة إلى رمز native_app_glue يجب إنشاؤها جنبًا إلى جنب مع رمز C/C++ الخاص بالتطبيق.

هناك تطبيقان (NativeActivity) يستخدمان native_app_glue التي يتم شحنها داخل NDK. يجب استبداله بإصدار GameActivity. من native_app_glue. بخلاف ذلك، تم توثيق جميع الخطوات البالغ عددها cmake. ينطبق دليل البدء:

  • استيراد مكتبة 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)
    
  • إزالة جميع الإشارات إلى رمز native_app_glue الخاص بـ NDK، مثل:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • إذا كنت تستخدم إصدار رمز المصدر، يُرجى تضمين مصدر GameActivity الملفات. وبخلاف ذلك، يمكنك تخطي هذه الخطوة.

    get_target_property(game-activity-include
                        game-activity::game-activity
                        INTERFACE_INCLUDE_DIRECTORIES)
    add_library(${PROJECT_NAME} SHARED
        main.cpp
        ${game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c
        ${game-activity-include}/game-activity/GameActivity.cpp
        ${game-activity-include}/game-text-input/gametextinput.cpp)
    

التغلب على مشكلة UnsatisfiedLinkError

في حال ظهور UnsatsifiedLinkError الدالة com.google.androidgamesdk.GameActivity.initializeNativeCode()، إضافة هذا الرمز إلى ملف CMakeLists.txt:

set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")

تحديثات رمز المصدر C/C++

اتّبِع الخطوات التالية لاستبدال مراجع NativeActivity في تطبيق من خلال GameActivity:

  • يمكنك استخدام "native_app_glue" الذي تم إصداره من خلال "GameActivity". البحث و استبدال كل استخدامات android_native_app_glue.h بـ:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • اضبط فلتر أحداث الحركة وفلتر الأحداث الرئيسية على NULL ليتمكّن تطبيقك تلقّي أحداث الإدخال من جميع أجهزة الإدخال. عادةً ما تقوم بذلك في الداخل دالة android_main():

    void android_main(android_app* app) {
        ... // other init code.
    
        android_app_set_key_event_filter(app, NULL);
        android_app_set_motion_event_filter(app, NULL);
    
        ... // additional init code, and game loop code.
    }
    
  • إزالة رمز AInputEvent ذي صلة واستبداله برمز GameActivity تنفيذ InputBuffer:

    while (true) {
        // Read all pending events.
        int events;
        struct android_poll_source* source;
    
        // If not animating, block forever waiting for events.
        // If animating, loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                                (void**)&source)) >= 0) {
           // Process this app cycle or inset change event.
           if (source) {
               source->process(source->app, source);
           }
    
              ... // Other processing.
    
           // Check if app is exiting.
           if (state->destroyRequested) {
               engine_term_display(&engine);
               return;
           }
        }
        // Process input events if there are any.
        engine_handle_input(state);
    
       if (engine.animating) {
           // Draw a game frame.
       }
    }
    
    // Implement input event handling function.
    static int32_t engine_handle_input(struct android_app* app) {
       auto* engine = (struct engine*)app->userData;
       auto ib = android_app_swap_input_buffers(app);
       if (ib && ib->motionEventsCount) {
           for (int i = 0; i < ib->motionEventsCount; i++) {
               auto *event = &ib->motionEvents[i];
               int32_t ptrIdx = 0;
               switch (event->action & AMOTION_EVENT_ACTION_MASK) {
                   case AMOTION_EVENT_ACTION_POINTER_DOWN:
                   case AMOTION_EVENT_ACTION_POINTER_UP:
                       // Retrieve the index for the starting and the ending of any secondary pointers
                       ptrIdx = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
                   case AMOTION_EVENT_ACTION_DOWN:
                   case AMOTION_EVENT_ACTION_UP:
                       engine->state.x = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X);
                       engine->state.y = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y);
                       break;
                    case AMOTION_EVENT_ACTION_MOVE:
                    // Process the move action: the new coordinates for all active touch pointers
                    // are inside the event->pointers[]. Compare with our internally saved
                    // coordinates to find out which pointers are actually moved. Note that there is
                    // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there
                    // might be multiple pointers moved at the same time).
                        ...
                       break;
               }
           }
           android_app_clear_motion_events(ib);
       }
    
       // Process the KeyEvent in a similar way.
           ...
    
       return 0;
    }
    
  • مراجعة وتحديث المنطق المرتبط بحدث NativeActivity AInputEvent كما هو موضّح في الخطوة السابقة، InputBuffer الخاص بـ GameActivity خارج حلقة ALooper_pollAll().

  • استبدال استخدام android_app::activity->clazz بـ android_app:: activity->javaGameActivity يعيد GameActivity تسمية مثيل Java GameActivity.

خطوات إضافية

تتناول الخطوات السابقة وظائف NativeActivity، ولكن يتضمّن GameActivity الميزات الإضافية التي قد ترغب في استخدامها:

نوصي باستكشاف هذه الميزات واستخدامها بما يتناسب مع الألعاب.

إذا كانت لديك أي أسئلة أو اقتراحات بشأن GameActivity أو تقرير AGDK آخر المكتب، يُرجى إنشاء خطأ لإعلامنا بذلك.