איך מתחילים להשתמש ב-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_static
prefab: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.