מעבר מ-NativeActivity חלק מ-Android Game Development Kit.
בדף הזה מוסבר איך עוברים מ-NativeActivity
ל-GameActivity
בפרויקט המשחק ל-Android.
GameActivity
מבוסס על NativeActivity
מהמסגרת של Android, עם שיפורים ותכונות חדשות:
- תמיכה ב-
Fragment
מ-Jetpack. - הוספנו תמיכה ב-
TextInput
כדי להקל על השילוב עם מקלדת וירטואלית. - טיפול באירועי מגע ובאירועי מקש בכיתה Java
GameActivity
במקום בממשקNativeActivity
onInputEvent
.
לפני ההעברה, מומלץ לקרוא את המדריך למתחילים, שבו מוסבר איך להגדיר ולשלב את GameActivity
בפרויקט.
עדכונים של סקריפטים ל-build של Java
GameActivity
מופץ כספרייה של Jetpack. חשוב לבצע את השלבים לעדכון סקריפט Gradle שמפורטים במדריך למתחילים:
מפעילים את ספריית Jetpack בקובץ
gradle.properties
של הפרויקט:android.useAndroidX=true
אפשר גם לציין גרסת Prefab באותו קובץ
gradle.properties
, לדוגמה:android.prefabVersion=2.0.0
מפעילים את התכונה Prefab בקובץ
build.gradle
של האפליקציה:android { ... // other configurations buildFeatures.prefab true }
מוסיפים את יחסי התלות
GameActivity
לאפליקציה:- מוסיפים את הספריות
core
ו-games-activity
. - אם רמת ה-API הנתמכת הנוכחית היא פחות מ-16, צריך לעדכן אותה ל-16 לפחות.
- מעדכנים את גרסת ה-SDK המתומצתת לגרסת ה-SDK שנדרשת לספרייה
games-activity
. בדרך כלל, כדי להשתמש ב-Jetpack נדרשת גרסת ה-SDK העדכנית ביותר בזמן ה-build של הגרסה.
קובץ
build.gradle
המעודכן עשוי להיראות כך:android { compiledSdkVersion 33 ... // other configurations. defaultConfig { minSdkVersion 16 } ... // other configurations. buildFeatures.prefab true } dependencies { implementation 'androidx.core:core:1.9.0' implementation 'androidx.games:games-activity:1.2.2' }
- מוסיפים את הספריות
עדכוני קוד ב-Kotlin או ב-Java
אפשר להשתמש ב-NativeActivity
כפעילות לטעינה בזמן ההפעלה, והיא יוצרת אפליקציה במסך מלא. בשלב זה, אי אפשר להשתמש ב-GameActivity כפעילות ההפעלה. אפליקציות צריכות להסיק סיווג מ-GameActivity
ולהשתמש בו כפעילות ההפעלה. צריך גם לבצע שינויים נוספים בהגדרות כדי ליצור אפליקציה במסך מלא.
השלבים הבאים מבוססים על ההנחה שהאפליקציה משתמשת ב-NativeActivity
כפעילות ההפעלה. אם לא, אפשר לדלג על רוב השאלות.
יוצרים קובץ Kotlin או Java שיארח את הפעילות החדשה של סטארט-אפ. לדוגמה, הקוד הבא יוצר את
MainActivity
כפעילות ההפעלה ומטעין את הספרייה הראשית של האפליקציה,libAndroidGame.so
:Kotlin
class MainActivity : GameActivity() { override fun onResume() { super.onResume() // Use the function recommended from the following page: // https://d.android.com/training/system-ui/immersive hideSystemBars() } companion object { init { System.loadLibrary("AndroidGame") } } }
Java
public class MainActivity extends GameActivity { protected void onResume() { super.onResume(); // Use the function recommended from // https://d.android.com/training/system-ui/immersive hideSystemBars(); } static { System.loadLibrary("AndroidGame"); } }
יוצרים עיצוב של אפליקציה במסך מלא בקובץ
res\values\themes.xml
:<resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item>" </style> </resources>
מחילים את העיצוב על האפליקציה בקובץ
AndroidManifest.xml
:<application android:theme=”@style/Application.Fullscreen”> <!-- other configurations not listed here. --> </application>
הוראות מפורטות לגבי מצב מסך מלא זמינות במדריך למצב immersive ובדוגמה להטמעה במאגר games-samples.
במדריך ההעברה הזה לא משנים את שם הספרייה המקורית. אם משנים את השם, חשוב לוודא ששמות הספריות המקומיות זהים בשלושת המיקומים הבאים:
קוד Kotlin או Java:
System.loadLibrary(“AndroidGame”)
AndroidManifest.xml
:<meta-data android:name="android.app.lib_name" android:value="AndroidGame" />
בתוך קובץ סקריפט ה-build של C/C++, לדוגמה
CMakeLists.txt
:add_library(AndroidGame ...)
עדכונים של סקריפטים ל-build ב-C/C++
בהוראות בקטע הזה נעשה שימוש ב-cmake
כדוגמה. אם האפליקציה שלכם משתמשת ב-ndk-build
, תצטרכו למפות אותה לפקודות המקבילות שמתוארות בדף המסמכים של ndk-build.
ההטמעה של GameActivity ב-C/C++ מספקת גרסה של קוד המקור. בגרסה 1.2.2 ואילך, קיימת גרסה סטטית של הספרייה. הספרייה הסטטית היא סוג הגרסה המומלץ.
הגרסה נארזת בתוך ה-AAR באמצעות הכלי prefab
. הקוד המקורי כולל את מקורות ה-C/C++ של GameActivity ואת הקוד native_app_glue
. צריך לבנות אותם יחד עם קוד ה-C/C++ של האפליקציה.
אפליקציות NativeActivity
כבר משתמשות בקוד native_app_glue
שנשלח ב-NDK. צריך להחליף אותו בגרסה של native_app_glue
ב-GameActivity. חוץ מזה, כל cmake
השלבים שמפורטים במדריך לתחילת העבודה רלוונטיים:
מייבאים את הספרייה הסטטית של C/C++ או את קוד המקור של C/++ לפרויקט באופן הבא.
ספרייה סטטית
בקובץ
CMakeLists.txt
של הפרויקט, מייבאים את הספרייה הסטטיתgame-activity
למודול ה-Prefabgame-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)
מסירים את כל ההפניות לקוד
native_app_glue
של NDK, למשל:${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c ... set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
אם אתם משתמשים במהדורת קוד המקור, צריך לכלול את קובצי המקור של
GameActivity
. אם לא, מדלגים על השלב הזה.get_target_property(game-activity-include game-activity::game-activity INTERFACE_INCLUDE_DIRECTORIES) add_library(${PROJECT_NAME} SHARED main.cpp ${game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c ${game-activity-include}/game-activity/GameActivity.cpp ${game-activity-include}/game-text-input/gametextinput.cpp)
פתרון לבעיה UnsatisfiedLinkError
אם נתקלתם ב-UnsatsifiedLinkError
לפונקציה com.google.androidgamesdk.GameActivity.initializeNativeCode()
, מוסיפים את הקוד הזה לקובץ CMakeLists.txt
:
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u \
Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")
עדכונים בקוד המקור של C/C++
כדי להחליף את ההפניות ל-NativeActivity
באפליקציה ב-GameActivity
:
משתמשים ב-
native_app_glue
שפורסם עםGameActivity
. מחפשים את כל השימוש ב-android_native_app_glue.h
ומחליפים אותו בקוד הבא:#include <game-activity/native_app_glue/android_native_app_glue.h>
מגדירים את מסנן אירועי התנועה ואת מסנן האירועים המרכזיים ל-
NULL
כדי שהאפליקציה תוכל לקבל אירועי קלט מכל מכשירי הקלט. בדרך כלל עושים זאת בתוך הפונקציהandroid_main()
:void android_main(android_app* app) { ... // other init code. android_app_set_key_event_filter(app, NULL); android_app_set_motion_event_filter(app, NULL); ... // additional init code, and game loop code. }
מסירים את הקוד שקשור ל-
AInputEvent
ומחליפים אותו בהטמעה שלInputBuffer
ב-GameActivity:while (true) { // Read all pending events. int events; struct android_poll_source* source; // If not animating, block forever waiting for events. // If animating, loop until all events are read, then continue // to draw the next frame of animation. while ((ALooper_pollOnce(engine.animating ? 0 : -1, nullptr, &events, (void**)&source)) >= 0) { // Process this app cycle or inset change event. if (source) { source->process(source->app, source); } ... // Other processing. // Check if app is exiting. if (state->destroyRequested) { engine_term_display(&engine); return; } } // Process input events if there are any. engine_handle_input(state); if (engine.animating) { // Draw a game frame. } } // Implement input event handling function. static int32_t engine_handle_input(struct android_app* app) { auto* engine = (struct engine*)app->userData; auto ib = android_app_swap_input_buffers(app); if (ib && ib->motionEventsCount) { for (int i = 0; i < ib->motionEventsCount; i++) { auto *event = &ib->motionEvents[i]; int32_t ptrIdx = 0; switch (event->action & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_POINTER_UP: // Retrieve the index for the starting and the ending of any secondary pointers ptrIdx = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_UP: engine->state.x = GameActivityPointerAxes_getAxisValue( &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X); engine->state.y = GameActivityPointerAxes_getAxisValue( &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y); break; case AMOTION_EVENT_ACTION_MOVE: // Process the move action: the new coordinates for all active touch pointers // are inside the event->pointers[]. Compare with our internally saved // coordinates to find out which pointers are actually moved. Note that there is // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there // might be multiple pointers moved at the same time). ... break; } } android_app_clear_motion_events(ib); } // Process the KeyEvent in a similar way. ... return 0; }
בודקים ומעדכנים את הלוגיקה שמצורפת ל-
AInputEvent
של NativeActivity. כפי שמוצג בשלב הקודם, העיבוד שלInputBuffer
ב-GameActivity מתבצע מחוץ ל-loopALooper_pollOnce()
.מחליפים את השימוש ב-
android_app::activity->clazz
ב-android_app:: activity->javaGameActivity
. GameActivity משנה את השם של מכונהGameActivity
ב-Java.
שלבים נוספים
השלבים הקודמים מתארים את הפונקציונליות של NativeActivity, אבל ל-GameActivity
יש תכונות נוספות שיכול להיות שתרצו להשתמש בהן:
- TextInput.
- שליטת משחקים.
- Fragment.
- פקודות InSet בחלון חדש שמוגדרות ב-NativeAppGlueAppCmd.
מומלץ לבדוק את התכונות האלה ולהשתמש בהן בהתאם למשחקים שלכם.
אם יש לכם שאלות או המלצות לגבי GameActivity או לגבי ספריות אחרות של AGDK, אתם יכולים ליצור דיווח על באג כדי להודיע לנו.