איך מתחילים להשתמש ב-GameActivity חלק מ-Android Game Development Kit.
במדריך הזה מוסבר איך להגדיר ולשלב את GameActivity ולטפל באירועים במשחק שלכם ל-Android.
GameActivity עוזרת להעביר משחקי C או C++ ל-Android על ידי פישוט התהליך של שימוש בממשקי API חשובים.
בעבר, NativeActivity היה הסיווג המומלץ למשחקים. GameActivity מחליף אותו כסיווג המומלץ למשחקים, ויש לו תאימות לאחור עד לרמת API 19.
דוגמה לשילוב של GameActivity אפשר למצוא במאגר הדוגמאות של משחקים.
לפני שמתחילים
אפשר להוריד הפצה מGameActivity גרסאות.
הגדרת ה-build
ב-Android, Activity משמש כנקודת הכניסה למשחק, ומספק גם את Window לציור בתוכו. הרבה משחקים מרחיבים את Activity באמצעות מחלקה משלהם ב-Java או ב-Kotlin כדי להתגבר על מגבלות ב-NativeActivity, תוך שימוש בקוד JNI כדי ליצור גשר לקוד המשחק ב-C או ב-C++.
GameActivity מציע את היכולות הבאות:
הוא יורש מ-
AppCompatActivity, ומאפשר לכם להשתמש ברכיבי הארכיטקטורה של Android Jetpack.הרינדור מתבצע ב-
SurfaceViewשמאפשר לכם ליצור אינטראקציה עם כל רכיב אחר בממשק המשתמש של Android.מטפל באירועי פעילות ב-Java. כך אפשר לשלב כל רכיב של ממשק משתמש של Android (כמו
EditText, WebViewאוAd) במשחק באמצעות ממשק C.מציעה C API דומה לספריית
NativeActivityו-android_native_app_glue.
GameActivity מופץ כארכיון Android (AAR). קובץ ה-AAR הזה מכיל את מחלקת Java שבה אתם משתמשים ב-AndroidManifest.xml, וגם את קוד המקור של C ו-C++ שמקשר בין צד Java של GameActivity לבין הטמעת C/C++ של האפליקציה. אם אתם משתמשים בגרסה GameActivity 1.2.2 ואילך, מסופקת גם ספרייה סטטית של C/C++. במקרים שבהם זה רלוונטי, מומלץ להשתמש בספרייה הסטטית במקום בקוד המקור.
צריך לכלול את קובצי המקור האלה או את הספרייה הסטטית כחלק מתהליך ה-build באמצעות Prefab, שחושף ספריות Native וקוד מקור לפרויקט CMake או ל-NDK build.
פועלים לפי ההוראות שבדף Jetpack Android Games כדי להוסיף את התלות בספריית
GameActivityלקובץbuild.gradleשל המשחק.כדי להפעיל את התכונה prefab, מבצעים את הפעולות הבאות עם Android Plugin Version (AGP) 4.1+:
- מוסיפים את הקוד הבא לבלוק
androidבקובץbuild.gradleשל המודול:
buildFeatures { prefab true }- בוחרים גרסת Prefab ומגדירים אותה לקובץ
gradle.properties:
android.prefabVersion=2.0.0אם אתם משתמשים בגרסאות קודמות של AGP, תוכלו לעיין במסמכי ה-prefab כדי לקבל הוראות להגדרה המתאימה.
- מוסיפים את הקוד הבא לבלוק
מייבאים את הספרייה הסטטית של C/C++ או את קוד המקור של C/++ לפרויקט באופן הבא.
ספרייה סטטית
בקובץ
CMakeLists.txtשל הפרויקט, מייבאים את הספרייה הסטטיתgame-activityלמודולgame-activity_staticprefab: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 מפעיל את הפעילות
מערכת Android מריצה קוד במופע של Activity על ידי הפעלת שיטות של קריאה חוזרת (callback) שתואמות לשלבים ספציפיים במחזור החיים של Activity. כדי שמערכת Android תפעיל את הפעילות ותתחיל את המשחק, צריך להצהיר על הפעילות עם המאפיינים המתאימים בקובץ Android Manifest. מידע נוסף זמין במאמר מבוא לפעילויות.
קובץ מניפסט של Android
לכל פרויקט אפליקציה צריך להיות קובץ AndroidManifest.xml בבסיס של קבוצת המקורות של הפרויקט. קובץ המניפסט מתאר מידע חיוני על האפליקציה שלכם לכלי הבנייה של Android, למערכת ההפעלה Android ול-Google Play. הנתונים האלה כוללים:
שם החבילה ומזהה האפליקציה לזיהוי ייחודי של המשחק ב-Google Play.
רכיבי אפליקציה כמו פעילויות, שירותים, מקלטי שידורים וספקי תוכן.
הרשאות לגישה לחלקים מוגנים במערכת או לאפליקציות אחרות.
תאימות המכשיר כדי לציין את דרישות החומרה והתוכנה של המשחק.
שם ספריית Native עבור
GameActivityו-NativeActivity(ברירת המחדל היא libmain.so).
הטמעה של GameActivity במשחק
יוצרים או מזהים את מחלקת הפעילות הראשית של Java (זו שמצוינת באלמנט
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), צריך להוסיף את ספריית Native אלAndroidManifest.xml:<meta-data android:name="android.app.lib_name" android:value="android-game" />
הטמעה של android_main
הספרייה
android_native_app_glueהיא ספריית קוד מקור שהמשחק משתמש בה כדי לנהל אירועים של מחזור החיים שלGameActivityב-thread נפרד, כדי למנוע חסימה ב-thread הראשי. כשמשתמשים בספרייה, רושמים את הקריאה החוזרת לטיפול באירועים של מחזור החיים, כמו אירועים של קלט מגע. הארכיון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_input_buffer באמצעות
android_app_swap_input_buffers()
בתוך לולאת המשחק. הם מכילים אירועים של תנועה ואירועים מרכזיים שהתרחשו מאז הפעם האחרונה שהתבצעה בדיקה. מספר האירועים שכלולים מאוחסן ב-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()ופונקציות קשורות אחרות בדוגמה ב-GitHub.כשמסיימים, חשוב לזכור לנקות את תור האירועים שטופלו:
android_app_clear_motion_events(mApp);
מקורות מידע נוספים
מידע נוסף על GameActivity זמין במקורות הבאים:
- נתוני גרסה של GameActivity ו-AGDK
- שימוש ב-GameTextInput ב-GameActivity.
- מדריך להעברת נתונים (מיגרציה) של NativeActivity
- מאמרי העזרה של GameActivity
- הטמעה של GameActivity.
כדי לדווח על באגים או לבקש תכונות חדשות ב-GameActivity, אפשר להשתמש בכלי למעקב אחר בעיות ב-GameActivity.