העברה מ-NativeActivity חלק מ-Android Game Development Kit.
בדף הזה מוסבר איך להעביר את הפרויקט של משחק Android מ-NativeActivity
ל-GameActivity
.
GameActivity
מבוסס על NativeActivity
מתוך מסגרת Android, עם שיפורים ותכונות חדשות:
- תמיכה ב-
Fragment
מ-Jetpack. - הוספנו תמיכה ב-
TextInput
כדי להקל על שילוב של מקלדת וירטואלית. - מטפל באירועי מגע ואירועי מקשים ב-
GameActivity
Java class במקום בממשק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 שעברה קומפילציה לגרסה שנדרשת בספרייה
games-activity
. בדרך כלל, כדי להשתמש ב-Jetpack צריך את גרסת ה-SDK העדכנית ביותר בזמן בניית הגרסה.
קובץ
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>
הוראות מפורטות לגבי מצב מסך מלא זמינות במדריך המקיף ודוגמה להטמעה זמינה במאגר הדוגמאות של משחקים.
מדריך ההעברה הזה לא משנה את השם של הספרייה המקורית. אם משנים את השם, צריך לוודא ששמות הספריות המקוריות זהים בשלושת המיקומים הבאים:
קוד Kotlin או Java:
System.loadLibrary(“AndroidGame”)
AndroidManifest.xml
:<meta-data android:name="android.app.lib_name" android:value="AndroidGame" />
בתוך קובץ הסקריפט של 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
למודול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)
מסירים את כל ההפניות לקוד
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; }
בודקים ומעדכנים את הלוגיקה שמצורפת ל-NativeActivity’s
AInputEvent
. כפי שמוצג בשלב הקודם, העיבוד של GameActivityInputBuffer
מתבצע מחוץ ללולאהALooper_pollOnce()
.החלפת השימוש ב-
android_app::activity->clazz
בשימוש ב-android_app:: activity->javaGameActivity
. GameActivity משנה את השם של מופע JavaGameActivity
.
שלבים נוספים
השלבים הקודמים מתייחסים לפונקציונליות של NativeActivity, אבל ל-GameActivity
יש תכונות נוספות שאולי תרצו להשתמש בהן:
- TextInput.
- בקר משחקים.
- Fragment.
- פקודות חדשות של InSets בחלון מוגדרות ב-NativeAppGlueAppCmd.
מומלץ להתנסות בתכונות האלה ולהטמיע אותן במשחקים שלכם לפי הצורך.
אם יש לכם שאלות או המלצות לגבי GameActivity או ספריות אחרות של AGDK, אתם יכולים ליצור באג כדי לעדכן אותנו.