انتقال از NativeActivity   بخشی از Android Game Development Kit.

این صفحه نحوه انتقال از NativeActivity به GameActivity را در پروژه بازی Android شما شرح می‌دهد.

‫GameActivity براساس NativeActivity از چارچوب Android است، با بهبودها و ویژگی‌های جدید:

  • از Fragment از Jetpack پشتیبانی می‌کند.
  • پشتیبانی TextInput را برای تسهیل ادغام صفحه‌کلید نرم‌افزاری اضافه می‌کند.
  • رویدادهای لمسی و کلیدی را در کلاس جاوا GameActivity به‌جای میانای NativeActivity onInputEvent مدیریت می‌کند.

قبل‌از انتقال، توصیه می‌کنیم راهنمای شروع به کار را بخوانید که نحوه راه‌اندازی و ادغام GameActivity را در پروژه‌تان شرح می‌دهد.

به‌روزرسانی‌های اسکریپت ساخت جاوا

GameActivity به‌عنوان کتابخانه Jetpack توزیع می‌شود. حتماً مراحل به‌روزرسانی اسکریپت Gradle را که در راهنمای شروع به کار شرح داده شده است اعمال کنید:

  1. کتابخانه Jetpack را در فایل gradle.properties پروژه‌تان فعال کنید:

    android.useAndroidX=true
    
  2. درصورت تمایل، نسخه Prefab را در همان فایل gradle.properties مشخص کنید، برای مثال:

    android.prefabVersion=2.0.0
    
  3. ویژگی Prefab را در فایل build.gradle برنامه‌تان فعال کنید:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. وابستگی GameActivity را به برنامه‌تان اضافه کنید:

    1. کتابخانه‌های core و games-activity را اضافه کنید.
    2. اگر حداقل سطح API پشتیبانی‌شده فعلی شما کمتر از ۱۶ است، آن را به حداقل ۱۶ به‌روز کنید.
    3. نسخه کیت توسعه نرم‌افزار کامپایل‌شده را به نسخه‌ای که کتابخانه games-activity نیاز دارد به‌روز کنید. Jetpack معمولاً به جدیدترین نسخه کیت توسعه نرم‌افزار در زمان ساخت نسخه پخش نیاز دارد.

    فایل 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، را بار می‌کند:

    کاتلین

    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")
           }
       }
    }

    جاوا

      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 نسخه کد منبع را ارائه کرده است. برای نسخه ۱.۲.۲ و نسخه‌های جدیدتر، نسخه کتابخانه ایستا ارائه شده است. کتابخانه ایستا نوع انتشار توصیه‌شده است.

نسخه پخش با ابزار کمکی prefab درون AAR بسته‌بندی شده است. کد بومی شامل منابع 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 را بردارید و آن را با پیاده‌سازی InputBuffer GameActivity جایگزین کنید:

    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_pollOnce(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;
    }
    
  • منطق پیوست‌شده به AInputEvent NativeActivity را بازبینی و به‌روز کنید. همان‌طور که در مرحله قبل نشان داده شد، پردازش InputBuffer GameActivity خارج از حلقه ALooper_pollOnce() است.

  • استفاده از android_app::activity->clazz را با android_app:: activity->javaGameActivity جایگزین کنید. GameActivity نام نمونه Java GameActivity را تغییر می‌دهد.

مراحل بیشتر

مراحل قبلی عملکرد NativeActivity را پوشش می‌دهد، اما GameActivity ویژگی‌های دیگری دارد که ممکن است بخواهید از آن‌ها استفاده کنید:

توصیه می‌کنیم این ویژگی‌ها را کاوش کنید و آن‌ها را به‌طور مناسب برای بازی‌هایتان اتخاذ کنید.

اگر سؤال یا توصیه‌ای برای GameActivity یا سایر کتابخانه‌های AGDK دارید، اشکالی ایجاد کنید تا به ما اطلاع دهید.