مهاجرت از NativeActivity بخشی از کیت توسعه بازی اندروید .

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

GameActivity بر اساس NativeActivity از چارچوب اندروید ساخته شده است، با پیشرفت‌ها و ویژگی‌های جدید:

  • از 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. نسخه SDK کامپایل شده را به نسخه‌ای که کتابخانه games-activity نیاز دارد، به‌روزرسانی کنید. جت‌پک معمولاً در زمان ساخت نسخه آزمایشی به آخرین نسخه 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'
    }
    

به‌روزرسانی‌های کد کاتلین یا جاوا

NativeActivity می‌تواند به عنوان یک فعالیت راه‌اندازی استفاده شود و یک برنامه تمام صفحه ایجاد کند. در حال حاضر، GameActivity نمی‌تواند به عنوان فعالیت راه‌اندازی استفاده شود. برنامه‌ها باید یک کلاس از GameActivity مشتق کنند و از آن به عنوان فعالیت راه‌اندازی استفاده کنند. همچنین برای ایجاد یک برنامه تمام صفحه باید تغییرات پیکربندی اضافی ایجاد کنید.

مراحل زیر فرض می‌کنند که برنامه شما NativeActivity به عنوان activity آغازین استفاده می‌کند. اگر اینطور نیست، می‌توانید از اکثر آنها صرف نظر کنید.

  1. یک فایل کاتلین یا جاوا برای میزبانی اکتیویتی راه‌اندازی جدید ایجاد کنید. برای مثال، کد زیر 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>
    

    برای دستورالعمل‌های دقیق برای حالت تمام صفحه، به راهنمای همه‌جانبه و پیاده‌سازی مثال در مخزن games-samples مراجعه کنید.

این راهنمای مهاجرت نام کتابخانه بومی را تغییر نمی‌دهد. اگر آن را تغییر می‌دهید، مطمئن شوید که نام کتابخانه‌های بومی در سه مکان زیر یکسان هستند:

  • کد کاتلین یا جاوا:

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

این نسخه درون AAR به همراه ابزار prefab قرار دارد. کد native شامل منابع C/C++ مربوط به GameActivity و کد native_app_glue است. آن‌ها باید همراه با کد C/C++ برنامه شما ساخته شوند.

برنامه‌های NativeActivity از قبل از کد native_app_glue که درون NDK قرار دارد استفاده می‌کنند. شما باید آن را با نسخه native_app_glue مربوط به GameActivity جایگزین کنید. به غیر از این، تمام مراحل cmake که در راهنمای شروع به کار مستند شده‌اند، اعمال می‌شوند:

  • کتابخانه استاتیک C/C++ یا کد منبع C/++ را به صورت زیر به پروژه خود وارد کنید.

    کتابخانه استاتیک

    در فایل CMakeLists.txt پروژه خود، کتابخانه استاتیک game-activity را به ماژول prefab مربوط به 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 را وارد کرده و آن را به پوشه target خود اضافه کنید. بسته 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 نام نمونه‌ی جاوای GameActivity را تغییر می‌دهد.

مراحل اضافی

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

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

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