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

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

GameActivity به شما کمک می کند تا با ساده کردن فرآیند استفاده از API های حیاتی، بازی C یا C++ خود را به اندروید بیاورید. قبلاً NativeActivity کلاس توصیه شده برای بازی ها بود. GameActivity به عنوان کلاس توصیه شده برای بازی ها جایگزین آن می شود و با سطح API 19 سازگار است.

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

قبل از شروع

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

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

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

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

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

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

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

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

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

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

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

  2. با انجام کارهای زیر با Android Plugin Version (AGP) 4.1+ ، prefab را فعال کنید:

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

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

  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 را وارد کرده و آن را به هدف خود اضافه کنید. بسته 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 Activity شما را راه اندازی می کند

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

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

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

GameActivity را در بازی خود پیاده کنید

  1. کلاس Java 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 در یک رشته جداگانه استفاده می کند تا از مسدود شدن در رشته اصلی شما جلوگیری کند. هنگام استفاده از کتابخانه، برای رسیدگی به رویدادهای چرخه حیات، مانند رویدادهای ورودی لمسی، پاسخ تماس را ثبت می کنید. بایگانی 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_pollAll(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. برای مطالعه بیشتر، اجرای مثال 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 ذخیره می شود.

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

    نمونه GitHub را برای پیاده سازی _cookEventForPointerIndex() و سایر توابع مرتبط ببینید.

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

    android_app_clear_motion_events(mApp);
    

منابع اضافی

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

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