เริ่มต้นใช้งาน GameActivity เป็นส่วนหนึ่งของชุดเครื่องมือพัฒนาเกม Android

คู่มือนี้จะอธิบายวิธีการตั้งค่าและผสานรวม GameActivity และจัดการกิจกรรมต่างๆ ใน Android เกม

GameActivity ช่วยให้คุณนำ C หรือ เกม C++ สำหรับ Android ด้วยการทำให้กระบวนการใช้ API ที่สำคัญง่ายขึ้น ก่อนหน้านี้ NativeActivity คือ คลาสที่แนะนำสำหรับเกม GameActivity แทนที่ค่านี้ตามที่แนะนำ สำหรับเกม และเข้ากันได้กับ API ระดับ 19

สำหรับตัวอย่างที่ผสานรวม GameActivity โปรดดูที่ ที่เก็บตัวอย่างเกม

ก่อนจะเริ่มต้น

ดูGameActivity รุ่นสำหรับ จัดสรรได้

ตั้งค่าบิลด์

ใน Android Activity จะทำหน้าที่เป็นรายการ ให้กับเกมของคุณ และยังให้ Window เพื่อวาดเขียนภายใน เกมหลายเกมสามารถเล่นได้มากมาย Activity นี้ให้มีคลาส Java หรือ Kotlin ของตนเองเพื่อเอาชนะข้อจำกัดใน NativeActivity ขณะใช้โค้ด JNI เพื่อบริดจ์ กับโค้ดเกม C หรือ C++

GameActivity มีความสามารถดังต่อไปนี้

  • รับค่าจาก AppCompatActivity ซึ่งช่วยให้คุณใช้สถาปัตยกรรม Android Jetpack คอมโพเนนต์

  • แสดงผลเป็น SurfaceView ที่ช่วยให้คุณดำเนินการต่อไปนี้ได้ พร้อมกับองค์ประกอบ UI อื่นๆ ของ Android

  • จัดการเหตุการณ์ของกิจกรรม Java ซึ่งทำให้ทุกองค์ประกอบ UI ของ Android (เช่น EditText WebView หรือ Ad) เป็น ผสานรวมเข้ากับเกมผ่านอินเทอร์เฟซ C

  • มี C API ที่คล้ายคลึงกับ NativeActivity และ android_native_app_glue ไลบรารี

GameActivity ได้รับการเผยแพร่เป็นที่เก็บถาวรของ Android (AAR) AAR นี้มีคลาส Java ที่ ที่คุณใช้ใน AndroidManifest.xml และ C และซอร์สโค้ด C++ ที่เชื่อมต่อด้าน Java ของ GameActivity กับแอปพลิเคชัน การใช้ C/C++ หากคุณใช้ GameActivity 1.2.2 หรือใหม่กว่า แท็ก C/C++ นอกจากนี้ ยังมีไลบรารีแบบคงที่ให้ด้วย เราขอแนะนำให้คุณใช้ ไลบรารีแบบคงที่แทนซอร์สโค้ด

รวมไฟล์ต้นฉบับเหล่านี้หรือไลบรารีแบบคงที่เป็นส่วนหนึ่งของ สร้างกระบวนการผ่าน Prefab ซึ่งจะแสดงไลบรารีเนทีฟและซอร์สโค้ดแก่ โปรเจ็กต์ CMake หรือบิลด์ NDK

  1. ทำตามวิธีการที่ Jetpack Android Games เพื่อเพิ่ม ทรัพยากร Dependency ของไลบรารี GameActivity ในไฟล์ build.gradle ของเกม

  2. เปิดใช้ Prefab โดยทำดังต่อไปนี้กับ เวอร์ชันปลั๊กอิน Android (AGP) 4.1 ขึ้นไป:

    • เพิ่มโค้ดต่อไปนี้ในบล็อก android ของไฟล์ build.gradle ของโมดูลของคุณ
    buildFeatures {
        prefab true
    }
    
    android.prefabVersion=2.0.0
    

    หากคุณใช้ AGP เวอร์ชันก่อนหน้า โปรดทำตาม เอกสาร Prefab เพื่อดูวิธีการกำหนดค่าที่สอดคล้องกัน

  3. นำเข้าไลบรารีแบบคงที่ของ 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)
    

    และรวมไฟล์ต่อไปนี้ลงใน CmakeLists.txt ของโปรเจ็กต์ด้วย GameActivity.cpp, GameTextInput.cpp และ android_native_app_glue.c

วิธีที่ Android เปิดตัวกิจกรรมของคุณ

ระบบ Android จะเรียกใช้โค้ดในอินสแตนซ์กิจกรรมโดยการเรียกใช้ Callback ที่สอดคล้องกับระยะที่เจาะจงในวงจรกิจกรรม อยู่ในคำสั่งซื้อ สำหรับ Android เพื่อเปิดกิจกรรมและเริ่มเกม คุณจะต้องประกาศ กิจกรรมของคุณด้วยแอตทริบิวต์ที่เหมาะสมในไฟล์ Android Manifest สำหรับข้อมูลเพิ่มเติม ดูข้อมูลได้ที่ ข้อมูลเบื้องต้นเกี่ยวกับกิจกรรม

ไฟล์ Manifest ของ Android

ทุกโปรเจ็กต์ของแอปต้องมี AndroidManifest.xml ที่ รูทของชุดแหล่งที่มาของโปรเจ็กต์ ไฟล์ Manifest อธิบายถึงสิ่งสำคัญ ข้อมูลแอปของคุณไปยังเครื่องมือสร้างของ Android, และ Google Play ฟีเจอร์เหล่านั้นรวมถึง

ใช้งาน GameActivity ในเกม

  1. สร้างหรือระบุคลาส Java กิจกรรมหลักของคุณ (คลาสที่ระบุใน activity ภายในไฟล์ AndroidManifest.xml) เปลี่ยนชั้นเรียนนี้เป็น ขยายเวลา GameActivity จากแพ็กเกจ com.google.androidgamesdk:

    import com.google.androidgamesdk.GameActivity;
    
    public class YourGameActivity extends GameActivity { ... }
    
  2. ตรวจสอบว่าไลบรารีเนทีฟโหลดขึ้นเมื่อเริ่มใช้บล็อกแบบคงที่ โดยทำดังนี้

    public class EndlessTunnelActivity extends GameActivity {
      static {
        // Load the native library.
        // The name "android-game" depends on your CMake configuration, must be
        // consistent here and inside AndroidManifect.xml
        System.loadLibrary("android-game");
      }
      ...
    }
    
  3. เพิ่มไลบรารีเนทีฟลงใน AndroidManifest.xml หากชื่อห้องสมุดของคุณไม่ใช่ชื่อเริ่มต้น (libmain.so):

    <meta-data android:name="android.app.lib_name"
     android:value="android-game" />
    

ติดตั้งใช้งาน android_main

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

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

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
    extern "C" {
        void android_main(struct android_app* state);
    };
    
    void android_main(struct android_app* app) {
        NativeEngine *engine = new NativeEngine(app);
        engine->GameLoop();
        delete engine;
    }
    
  2. ประมวลผล android_app ใน Game Loop หลักของคุณ เช่น แบบสำรวจและการจัดการ เหตุการณ์ในวงจรของแอปที่กำหนดไว้ใน NativeAppGlueAppCmd ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้ลงทะเบียนฟังก์ชัน _hand_cmd_proxy เป็น เครื่องจัดการ NativeAppGlueAppCmd แล้วสำรวจเหตุการณ์รอบแอป แล้วส่งไปยัง เครื่องจัดการที่ลงทะเบียน(ใน android_app::onAppCmd) สำหรับการประมวลผล:

    void NativeEngine::GameLoop() {
      mApp->userData = this;
      mApp->onAppCmd = _handle_cmd_proxy;  // register your command handler.
      mApp->textInputState = 0;
    
      while (1) {
        int events;
        struct android_poll_source* source;
    
        // If not animating, block until we get an event;
        // If animating, don't block.
        while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,
          (void **) &source)) >= 0) {
            if (source != NULL) {
                // process events, native_app_glue internally sends the outstanding
                // application lifecycle events to mApp->onAppCmd.
                source->process(source->app, source);
            }
            if (mApp->destroyRequested) {
                return;
            }
        }
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  3. หากต้องการอ่านเพิ่มเติม ให้ศึกษาการใช้งานอุโมงค์ข้อมูลไม่มีที่สิ้นสุด (Endless Tunnel) ตัวอย่าง NDK ความแตกต่างหลักๆ คือวิธีจัดการเหตุการณ์ดังที่แสดงใน หัวข้อถัดไป

จัดการเหตุการณ์

หากต้องการเปิดใช้เหตุการณ์การป้อนข้อมูลเพื่อเข้าถึงแอปของคุณ ให้สร้างและลงทะเบียนเหตุการณ์ ตัวกรองที่มี android_app_set_motion_event_filter และ android_app_set_key_event_filter โดยค่าเริ่มต้น ไลบรารี native_app_glue จะอนุญาตเฉพาะเหตุการณ์การเคลื่อนไหวจาก หน้าจอสัมผัสต้นทาง อินพุต อย่าลืมดูเอกสารอ้างอิง และรหัสการใช้ android_native_app_glue เพื่อดูรายละเอียด

หากต้องการจัดการเหตุการณ์การป้อนข้อมูล โปรดดู android_input_buffer ด้วย android_app_swap_input_buffers() ใน Game Loop ของคุณ ซึ่งมีเหตุการณ์การเคลื่อนไหวและเหตุการณ์สําคัญที่เกิดขึ้นตั้งแต่ครั้งล่าสุด มีแบบสำรวจ จำนวนเหตุการณ์ที่จัดเก็บไว้ใน motionEventsCount และ keyEventsCount ตามลำดับ

  1. ทำซ้ำและจัดการแต่ละเหตุการณ์ใน Game Loop ในตัวอย่างนี้ โค้ดต่อไปนี้จะทำซ้ำ motionEvents และจัดการผ่าน handle_event

    android_input_buffer* inputBuffer = android_app_swap_input_buffers(app);
    if (inputBuffer && inputBuffer->motionEventsCount) {
        for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) {
            GameActivityMotionEvent* motionEvent = &inputBuffer->motionEvents[i];
    
            if (motionEvent->pointerCount > 0) {
                const int action = motionEvent->action;
                const int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
                // Initialize pointerIndex to the max size, we only cook an
                // event at the end of the function if pointerIndex is set to a valid index range
                uint32_t pointerIndex = GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT;
                struct CookedEvent ev;
                memset(&ev, 0, sizeof(ev));
                ev.motionIsOnScreen = motionEvent->source == AINPUT_SOURCE_TOUCHSCREEN;
                if (ev.motionIsOnScreen) {
                    // use screen size as the motion range
                    ev.motionMinX = 0.0f;
                    ev.motionMaxX = SceneManager::GetInstance()->GetScreenWidth();
                    ev.motionMinY = 0.0f;
                    ev.motionMaxY = SceneManager::GetInstance()->GetScreenHeight();
                }
    
                switch (actionMasked) {
                    case AMOTION_EVENT_ACTION_DOWN:
                        pointerIndex = 0;
                        ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                        break;
                    case AMOTION_EVENT_ACTION_POINTER_DOWN:
                        pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                                       >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
                        ev.type = COOKED_EVENT_TYPE_POINTER_DOWN;
                        break;
                    case AMOTION_EVENT_ACTION_UP:
                        pointerIndex = 0;
                        ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                        break;
                    case AMOTION_EVENT_ACTION_POINTER_UP:
                        pointerIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                                       >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
                        ev.type = COOKED_EVENT_TYPE_POINTER_UP;
                        break;
                    case AMOTION_EVENT_ACTION_MOVE: {
                        // Move includes all active pointers, so loop and process them here,
                        // we do not set pointerIndex since we are cooking the events in
                        // this loop rather than at the bottom of the function
                        ev.type = COOKED_EVENT_TYPE_POINTER_MOVE;
                        for (uint32_t i = 0; i < motionEvent->pointerCount; ++i) {
                            _cookEventForPointerIndex(motionEvent, callback, ev, i);
                        }
                        break;
                    }
                    default:
                        break;
                }
    
                // Only cook an event if we set the pointerIndex to a valid range, note that
                // move events cook above in the switch statement.
                if (pointerIndex != GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT) {
                    _cookEventForPointerIndex(motionEvent, callback,
                                              ev, pointerIndex);
                }
            }
        }
        android_app_clear_motion_events(inputBuffer);
    }
    

    โปรดดู ตัวอย่างของ GitHub สำหรับการใช้ _cookEventForPointerIndex() และ ที่เกี่ยวข้อง

  2. เมื่อคุณทำเสร็จแล้ว อย่าลืมล้างคิวของกิจกรรมที่คุณเพิ่ง ที่จัดการ:

    android_app_clear_motion_events(mApp);
    

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ GameActivity ได้ที่บทความต่อไปนี้

หากต้องการรายงานข้อบกพร่องหรือขอฟีเจอร์ใหม่ไปยัง GameActivity ให้ใช้เครื่องมือติดตามปัญหา GameActivity