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