با 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 شما قرار میدهد.
برای افزودن وابستگی کتابخانه
GameActivityبه فایلbuild.gradleبازی خود، دستورالعملهای صفحه Jetpack Android Games را دنبال کنید.با انجام مراحل زیر در نسخه افزونه اندروید (AGP) 4.1+، prefab را فعال کنید:
- کد زیر را به بلوک
androidاز فایلbuild.gradleماژول خود اضافه کنید:
buildFeatures { prefab true }- یک نسخه Prefab انتخاب کنید و آن را روی فایل
gradle.propertiesتنظیم کنید:
android.prefabVersion=2.0.0اگر از نسخههای قبلی AGP استفاده میکنید، برای دستورالعملهای پیکربندی مربوطه ، مستندات prefab را دنبال کنید.
- کد زیر را به بلوک
کتابخانه استاتیک 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 در ریشه مجموعه منابع پروژه داشته باشد. فایل مانیفست اطلاعات ضروری در مورد اپلیکیشن شما را برای ابزارهای ساخت اندروید، سیستم عامل اندروید و گوگل پلی شرح میدهد. این اطلاعات شامل موارد زیر است:
نام بسته و شناسه برنامه برای شناسایی منحصر به فرد بازی شما در Google Play.
اجزای برنامه مانند فعالیتها، سرویسها، گیرندههای پخش و ارائه دهندگان محتوا.
مجوزهای دسترسی به بخشهای محافظتشده سیستم یا سایر برنامهها.
سازگاری دستگاه برای مشخص کردن نیازهای سختافزاری و نرمافزاری بازی شما.
نام کتابخانهی اصلی برای
GameActivityوNativeActivity( بهطور پیشفرض libmain.so است ).
پیادهسازی GameActivity در بازی شما
کلاس جاوای activity اصلی خود را ایجاد یا شناسایی کنید (همان کلاسی که در عنصر
activityدرون فایلAndroidManifest.xmlشما مشخص شده است). این کلاس را طوری تغییر دهید کهGameActivityاز پکیجcom.google.androidgamesdkارث بری کند:import com.google.androidgamesdk.GameActivity; public class YourGameActivity extends GameActivity { ... }مطمئن شوید که کتابخانه بومی شما در ابتدا با استفاده از یک بلوک استاتیک بارگذاری شده است:
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"); } ... }اگر نام کتابخانه شما نام پیشفرض (
libmain.so) نیست، کتابخانه بومی خود را بهAndroidManifest.xmlاضافه کنید:<meta-data android:name="android.app.lib_name" android:value="android-game" />
پیادهسازی android_main
کتابخانه
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; }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(); } } }برای مطالعه بیشتر، پیادهسازی مثال 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 ذخیره میشوند.
هر رویداد را در حلقه بازی خود تکرار و مدیریت کنید. در این مثال، کد زیر
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()و سایر توابع مرتبط، به نمونه گیتهاب مراجعه کنید.وقتی کارتان تمام شد، به یاد داشته باشید که صف رویدادهایی را که تازه مدیریت کردهاید، پاک کنید:
android_app_clear_motion_events(mApp);
منابع اضافی
برای کسب اطلاعات بیشتر در مورد GameActivity ، به موارد زیر مراجعه کنید:
- یادداشتهای انتشار GameActivity و AGDK
- از GameTextInput در GameActivity استفاده کنید .
- راهنمای مهاجرت NativeActivity .
- مستندات مرجع GameActivity .
- پیادهسازی GameActivity
برای گزارش اشکالات یا درخواست ویژگیهای جدید به GameActivity، از ردیاب مشکلات GameActivity استفاده کنید.