ย้ายข้อมูลจาก NativeActivity   ส่วนหนึ่งของ Android Game Development Kit

หน้านี้จะอธิบายวิธีย้ายข้อมูลจาก NativeActivity ไปยัง GameActivity ในโปรเจ็กต์เกม Android

GameActivity อิงตาม NativeActivity จากเฟรมเวิร์ก Android โดยมีการเพิ่มประสิทธิภาพและฟีเจอร์ใหม่ๆ ดังนี้

  • รองรับ Fragment จาก Jetpack
  • เพิ่มการรองรับ TextInput เพื่ออำนวยความสะดวกในการผสานรวมแป้นพิมพ์ซอฟต์แวร์
  • จัดการเหตุการณ์การแตะและเหตุการณ์คีย์ในGameActivityคลาส Java แทน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. เพิ่มทรัพยากร Dependency ของ GameActivity ลงในแอปพลิเคชัน

    1. เพิ่มไลบรารี core และ games-activity
    2. หากระดับ API ขั้นต่ำที่รองรับในปัจจุบันต่ำกว่า 16 ให้อัปเดตเป็นอย่างน้อย 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 อยู่แล้ว คุณต้องแทนที่ด้วยเวอร์ชัน 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โค้ดที่เกี่ยวข้องออก แล้วแทนที่ด้วยการติดตั้งใช้งาน InputBufferGameActivity ดังนี้

    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 AInputEvent ดังที่แสดงในขั้นตอนก่อนหน้า InputBuffer การประมวลผลของ GameActivity จะอยู่นอกลูป ALooper_pollOnce()

  • แทนที่การใช้งาน android_app::activity->clazz ด้วย android_app:: activity->javaGameActivity GameActivity จะเปลี่ยนชื่ออินสแตนซ์ GameActivity ของ Java

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

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

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

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