با GameActivity که بخشی از کیت توسعه بازی اندروید است، شروع کنید.

این راهنما نحوه تنظیم و ادغام GameActivity و مدیریت رویدادها در بازی اندروید شما را شرح می‌دهد.

GameActivity با ساده‌سازی فرآیند استفاده از APIهای حیاتی، به شما کمک می‌کند تا بازی‌های C یا C++ خود را به اندروید بیاورید. پیش از این، NativeActivity کلاس پیشنهادی برای بازی‌ها بود. GameActivity جایگزین آن به عنوان کلاس پیشنهادی برای بازی‌ها شده است و با API سطح ۱۹ سازگار است.

برای نمونه‌ای که GameActivity را ادغام می‌کند، به مخزن games-samples مراجعه کنید.

قبل از شروع

برای دریافت توزیع، به نسخه‌های GameActivity مراجعه کنید.

ساخت خود را تنظیم کنید

در اندروید، یک Activity به عنوان نقطه ورود برای بازی شما عمل می‌کند و همچنین Window برای طراحی درون آن فراهم می‌کند. بسیاری از بازی‌ها این Activity با کلاس Java یا Kotlin خود گسترش می‌دهند تا محدودیت‌های NativeActivity را از بین ببرند و در عین حال از کد JNI برای اتصال به کد بازی C یا C++ خود استفاده کنند.

GameActivity قابلیت‌های زیر را ارائه می‌دهد:

  • از AppCompatActivity ارث‌بری می‌کند و به شما امکان می‌دهد از کامپوننت‌های معماری Android Jetpack استفاده کنید.

  • به یک SurfaceView رندر می‌شود که به شما امکان می‌دهد با هر عنصر رابط کاربری اندروید دیگری ارتباط برقرار کنید.

  • رویدادهای فعالیت جاوا را مدیریت می‌کند. این به هر عنصر رابط کاربری اندروید (مانند EditText ، WebView یا Ad ) اجازه می‌دهد تا از طریق رابط C با بازی شما ادغام شود.

  • یک API زبان C مشابه NativeActivity و کتابخانه android_native_app_glue ارائه می‌دهد.

GameActivity به عنوان یک بایگانی اندروید (AAR) توزیع شده است. این AAR شامل کلاس جاوا است که شما در AndroidManifest.xml خود استفاده می‌کنید، و همچنین کد منبع C و C++ که سمت جاوای GameActivity را به پیاده‌سازی C/C++ برنامه متصل می‌کند. اگر از GameActivity 1.2.2 یا بالاتر استفاده می‌کنید، کتابخانه استاتیک C/C++ نیز ارائه شده است. در صورت لزوم، توصیه می‌کنیم از کتابخانه استاتیک به جای کد منبع استفاده کنید.

این فایل‌های منبع یا کتابخانه استاتیک را به عنوان بخشی از فرآیند ساخت خود از طریق Prefab اضافه کنید، که کتابخانه‌ها و کد منبع بومی را در پروژه CMake یا ساخت NDK شما قرار می‌دهد.

  1. برای افزودن وابستگی کتابخانه GameActivity به فایل build.gradle بازی خود، دستورالعمل‌های صفحه Jetpack Android Games را دنبال کنید.

  2. با انجام مراحل زیر در نسخه افزونه اندروید (AGP) 4.1+، prefab را فعال کنید:

    • کد زیر را به بلوک android از فایل build.gradle ماژول خود اضافه کنید:
    buildFeatures {
        prefab true
    }
    
    • یک نسخه Prefab انتخاب کنید و آن را روی فایل gradle.properties تنظیم کنید:
    android.prefabVersion=2.0.0
    

    اگر از نسخه‌های قبلی AGP استفاده می‌کنید، برای دستورالعمل‌های پیکربندی مربوطه ، مستندات prefab را دنبال کنید.

  3. کتابخانه استاتیک 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)
    

    همچنین، فایل‌های زیر را در CmakeLists.txt پروژه خود قرار دهید: GameActivity.cpp ، GameTextInput.cpp و android_native_app_glue.c .

چگونه اندروید Activity شما را اجرا می‌کند

سیستم اندروید با فراخوانی متدهای فراخوانی که مربوط به مراحل خاصی از چرخه حیات اکتیویتی هستند، کد را در نمونه اکتیویتی شما اجرا می‌کند. برای اینکه اندروید بتواند اکتیویتی شما را اجرا کند و بازی شما را شروع کند، باید اکتیویتی خود را با ویژگی‌های مناسب در مانیفست اندروید اعلام کنید. برای اطلاعات بیشتر، به مقدمه‌ای بر اکتیویتی‌ها مراجعه کنید.

مانیفست اندروید

هر پروژه اپلیکیشن باید یک فایل AndroidManifest.xml در ریشه مجموعه منابع پروژه داشته باشد. فایل مانیفست اطلاعات ضروری در مورد اپلیکیشن شما را برای ابزارهای ساخت اندروید، سیستم عامل اندروید و گوگل پلی شرح می‌دهد. این اطلاعات شامل موارد زیر است:

پیاده‌سازی GameActivity در بازی شما

  1. کلاس جاوای activity اصلی خود را ایجاد یا شناسایی کنید (همان کلاسی که در عنصر activity درون فایل AndroidManifest.xml شما مشخص شده است). این کلاس را طوری تغییر دهید که GameActivity از پکیج com.google.androidgamesdk ارث بری کند:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. مطمئن شوید که کتابخانه بومی شما در ابتدا با استفاده از یک بلوک استاتیک بارگذاری شده است:

    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");
      }
      ...
    }
    
  3. اگر نام کتابخانه شما نام پیش‌فرض ( libmain.so ) نیست، کتابخانه بومی خود را به AndroidManifest.xml اضافه کنید:

    <meta-data android:name="android.app.lib_name"
     android:value="android-game" />
    

پیاده‌سازی android_main

  1. کتابخانه android_native_app_glue یک کتابخانه کد منبع است که بازی شما برای مدیریت رویدادهای چرخه عمر GameActivity در یک رشته جداگانه استفاده می‌کند تا از مسدود شدن در رشته اصلی شما جلوگیری شود. هنگام استفاده از این کتابخانه، شما یک تابع فراخوانی (callback) برای مدیریت رویدادهای چرخه عمر، مانند رویدادهای ورودی لمسی، ثبت می‌کنید. آرشیو 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;
    }
    
  2. 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();
        }
      }
    }
    
  3. برای مطالعه بیشتر، پیاده‌سازی مثال Endless Tunnel NDK را مطالعه کنید. تفاوت اصلی در نحوه مدیریت رویدادها خواهد بود، همانطور که در بخش بعدی نشان داده شده است.

مدیریت رویدادها

برای فعال کردن دسترسی رویدادهای ورودی به برنامه‌تان، فیلترهای رویداد خود را با android_app_set_motion_event_filter و android_app_set_key_event_filter ایجاد و ثبت کنید. به طور پیش‌فرض، کتابخانه native_app_glue فقط رویدادهای حرکتی را از ورودی SOURCE_TOUCHSCREEN مجاز می‌داند. برای جزئیات بیشتر، حتماً سند مرجع و کد پیاده‌سازی android_native_app_glue را بررسی کنید.

برای مدیریت رویدادهای ورودی، با استفاده از android_app_swap_input_buffers() در حلقه بازی خود، به android_input_buffer ارجاع دهید. این رویدادها شامل رویدادهای حرکتی و رویدادهای کلیدی هستند که از آخرین باری که مورد بررسی قرار گرفته‌اند، رخ داده‌اند. تعداد رویدادهای موجود به ترتیب در motionEventsCount و keyEventsCount ذخیره می‌شوند.

  1. هر رویداد را در حلقه بازی خود تکرار و مدیریت کنید. در این مثال، کد زیر 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);
    }
    

    برای پیاده‌سازی تابع _cookEventForPointerIndex() و سایر توابع مرتبط، به نمونه گیت‌هاب مراجعه کنید.

  2. وقتی کارتان تمام شد، به یاد داشته باشید که صف رویدادهایی را که تازه مدیریت کرده‌اید، پاک کنید:

    android_app_clear_motion_events(mApp);
    

منابع اضافی

برای کسب اطلاعات بیشتر در مورد GameActivity ، به موارد زیر مراجعه کنید:

برای گزارش اشکالات یا درخواست ویژگی‌های جدید به GameActivity، از ردیاب مشکلات GameActivity استفاده کنید.