העברה מ-NativeActivity חלק מ-Android Game Development Kit.

בדף הזה נסביר איך לבצע העברה מ NativeActivity עד GameActivity בפרויקט המשחק ל-Android.

GameActivity מבוסס על NativeActivity מ-Android עם שיפורים ותכונות חדשות:

  • תמיכה בערך Fragment מ-Jetpack.
  • הוספת תמיכה ב-TextInput כדי לאפשר שילוב של מקלדת עם יכולת שחזור.
  • טיפול באירועים מרכזיים ובאירועים מרכזיים ברמת Java GameActivity במקום ממשק NativeActivity onInputEvent.

לפני ההעברה, מומלץ לקרוא את מדריך לתחילת העבודה, שמתאר כדי להגדיר ולשלב את GameActivity בפרויקט.

עדכונים לגבי סקריפט build של Java

GameActivity מופץ בתור ספריית Jetpack. צריך להקפיד להחיל את שלבי העדכון של הסקריפט של Gradle במדריך לתחילת העבודה:

  1. מפעילים את ספריית Jetpack בקובץ gradle.properties של הפרויקט:

    android.useAndroidX=true
    
  2. אפשר לציין גרסה Prefab באותו קובץ gradle.properties, לדוגמה:

    android.prefabVersion=2.0.0
    
  3. הפעלת התכונה Prefab בקובץ build.gradle של האפליקציה:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. מוסיפים את התלות GameActivity לאפליקציה:

    1. מוסיפים את הספריות core ו-games-activity.
    2. אם רמת ה-API המינימלית הנתמכת הנוכחית שלך היא פחות מ-16, צריך לעדכן אותה. ל-16 תווים לפחות.
    3. צריך לעדכן את גרסת ה-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 בתור ההפעלה פעילות. אם זה לא המצב, אפשר לדלג על רובם.

  1. ליצור קובץ Kotlin או Java לאירוח פעילות ההפעלה החדשה. לדוגמה, הקוד הבא יוצר את MainActivity כפעילות ההפעלה טוען את ספריית ה-Native הראשית של האפליקציה, 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");
          }
      }
    
  2. יצירת עיצוב אפליקציה במסך מלא בקובץ 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>
    
  3. מחילים את העיצוב על האפליקציה בקובץ 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" />
    
  • בתוך קובץ סקריפט ה-build C/C++, לדוגמה CMakeLists.txt:

    add_library(AndroidGame ...)
    

עדכונים לגבי סקריפט build של C/C++

בהוראות בקטע הזה נעשה שימוש ב-cmake כדוגמה. אם הבקשה שלכם משתמשת ב-ndk-build, צריך למפות אותן לפקודות המקבילות שמתוארות כאן ndk-build לדף התיעוד.

הטמעת C/C++ של GameActivity סיפקה גרסה של קוד מקור. לגרסה 1.2.2 ואילך יש גרסת ספרייה סטטית. ההגדרות הסטטיות הוא סוג ההפצה המומלץ.

יחידת ההפצה ארוזה ב-AAR עם prefab של Google. הקוד המקורי כולל את מקורות C/C++ של GameActivity קוד native_app_glue. הם צריכים להיבנות יחד עם בקוד C/C++ של האפליקציה.

NativeActivity אפליקציות כבר משתמשות בnative_app_glue הקוד נשלח בתוך NDK. יש להחליף אותה בגרסה של GameActivity מתוך native_app_glue. חוץ מזה, כל cmake השלבים מתועדים ב- מומלץ לעיין במדריך לתחילת העבודה:

  • מייבאים את הספרייה הסטטית 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)
    
  • מסירים את כל ההפניות לקוד 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) ולהחליף אותו בקוד של GameActivity הטמעת InputBuffer:

    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_pollAll(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 AInputEvent כמו שמוצג בשלב הקודם, InputBuffer של GameActivity העיבוד נמצא מחוץ ללולאה של ALooper_pollAll().

  • החלפת השימוש ב-android_app::activity->clazz ב- android_app:: activity->javaGameActivity. השם של GameActivity השתנה מופע של Java GameActivity.

שלבים נוספים

השלבים הקודמים מתייחסים לפונקציונליות של NativeActivity, אבל ב-GameActivity לגבי פיצ'רים נוספים שאולי תרצו להשתמש בהם:

מומלץ לבדוק את התכונות האלה וליישם אותן בהתאם משחקים.

אם יש לך שאלות או המלצות לגבי GameActivity או AGDK אחר ספריות, ליצור באג כדי לדווח לנו.