ย้ายข้อมูลจาก NativeActivity เป็นส่วนหนึ่งของชุดเครื่องมือพัฒนาเกม Android

หน้านี้อธิบายวิธีย้ายข้อมูลจาก NativeActivity ถึง GameActivity ในโปรเจ็กต์เกม Android ของคุณ

GameActivity อิงตามNativeActivityจาก Android พร้อมการปรับปรุงและฟีเจอร์ใหม่ๆ ดังนี้

  • รองรับ Fragment จาก Jetpack
  • เพิ่มการสนับสนุน TextInput เพื่ออำนวยความสะดวกในการผสานรวมแป้นพิมพ์เสมือน
  • จัดการการแตะและเหตุการณ์สำคัญในคลาส Java GameActivity แทน อินเทอร์เฟซ NativeActivity onInputEvent

ก่อนย้ายข้อมูล เราขอแนะนำให้อ่าน คู่มือเริ่มต้นใช้งานซึ่งอธิบายวิธีที่ เพื่อตั้งค่าและผสานรวม GameActivity ในโปรเจ็กต์ของคุณ

การอัปเดตสคริปต์บิลด์ของ 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 โปรดอัปเดตระดับ API ถึง 16 เป็นอย่างน้อย
    3. อัปเดตเวอร์ชัน 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 เป็นโปรแกรมเริ่มต้น กิจกรรม หากมิได้เป็นเช่นนั้น คุณสามารถข้ามขั้นตอนส่วนใหญ่ได้

  1. สร้างไฟล์ 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");
          }
      }
    
  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>
    

    ดูคำแนะนำอย่างละเอียดสำหรับโหมดเต็มหน้าจอได้ที่ คู่มืออย่างละเอียดและตัวอย่างการใช้งานใน ที่เก็บตัวอย่างเกม

คำแนะนำการย้ายข้อมูลนี้ไม่ได้เปลี่ยนชื่อไลบรารีเนทีฟ หากคุณเปลี่ยน ให้ตรวจสอบว่าชื่อไลบรารีเนทีฟสอดคล้องกันใน 3 รายการต่อไปนี้ สถานที่ตั้ง:

  • โค้ด 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 ...)
    

การอัปเดตสคริปต์บิลด์ของ C/C++

วิธีการในส่วนนี้ใช้ cmake เป็นตัวอย่าง หากแอปพลิเคชันของคุณ ใช้ ndk-build คุณจะต้องจับคู่ฟังก์ชันดังกล่าวกับคำสั่งที่เทียบเท่าตามที่อธิบายไว้ใน หน้าเอกสารประกอบเกี่ยวกับ ndk-build

การใช้งาน C/C++ ของ GameActivity คือการเปิดตัวซอร์สโค้ด สำหรับเวอร์ชัน 1.2.2 เป็นต้นไป จะมีรุ่นไลบรารีแบบคงที่ให้บริการ แบบคงที่ คือประเภทรุ่นที่แนะนำ

รุ่นนี้อัดแน่นอยู่ใน AAR พร้อม prefab ยูทิลิตี โค้ดแบบเนทีฟประกอบด้วยแหล่งที่มา 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 ไว้ในโมดูล Prefab 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)
    

วิธีแก้ปัญหา Uns DFPLinkError

หากคุณพบ 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 ได้เปลี่ยนชื่อ อินสแตนซ์ GameActivity ของ Java

ขั้นตอนเพิ่มเติม

ขั้นตอนก่อนหน้านี้ครอบคลุมฟังก์ชันของ NativeActivity แต่ GameActivity มี ฟีเจอร์เพิ่มเติมที่คุณอาจต้องการใช้:

เราขอแนะนำให้คุณสำรวจฟีเจอร์เหล่านี้และนำไปใช้ตามความเหมาะสม เกม

หากมีคำถามหรือคำแนะนำเกี่ยวกับ GameActivity หรือ AGDK อื่นๆ เพื่อสร้างข้อบกพร่องเพื่อแจ้งให้เราทราบ