จัดการการทำงานของตัวควบคุม

ที่ระดับระบบ Android จะรายงานรหัสเหตุการณ์อินพุตจากตัวควบคุมเกมเป็นรหัสคีย์และค่าแกนของ Android ในเกม คุณสามารถรับรหัสและค่าเหล่านี้ แล้วแปลงเป็นการดำเนินการที่เฉพาะเจาะจงในเกม

เมื่อผู้เล่นเชื่อมต่อเกมคอนโทรลเลอร์ด้วยตนเองหรือจับคู่แบบไร้สายกับอุปกรณ์ที่ใช้ Android ของตน ระบบจะตรวจจับตัวควบคุมนั้นเป็นอุปกรณ์อินพุตโดยอัตโนมัติและเริ่มรายงานเหตุการณ์อินพุต เกมรับเหตุการณ์อินพุตเหล่านี้ได้โดยการใช้เมธอด Callback ต่อไปนี้ใน Activity ที่ใช้งานอยู่หรือ View ที่โฟกัส (คุณควรใช้ Callback สำหรับ Activity หรือ View แต่อย่าใช้ทั้ง 2 รายการ)

  • จาก Activity
    • dispatchGenericMotionEvent(android.view. MotionEvent)

      เรียกใช้เพื่อประมวลผลเหตุการณ์การเคลื่อนไหวทั่วไป เช่น การเคลื่อนที่ของจอยสติ๊ก

    • dispatchKeyEvent(android.view.KeyEvent)

      เรียกใช้เพื่อประมวลผลเหตุการณ์สำคัญ เช่น การกดหรือปล่อยเกมแพดหรือปุ่ม D-pad

  • จาก View
    • onGenericMotionEvent(android.view.MotionEvent)

      เรียกใช้เพื่อประมวลผลเหตุการณ์การเคลื่อนไหวทั่วไป เช่น การเคลื่อนไหวของจอยสติ๊ก

    • onKeyDown(int, android.view.KeyEvent)

      เรียกใช้เพื่อประมวลผลการกดแป้นจริง เช่น เกมแพดหรือปุ่ม D-pad

    • onKeyUp(int, android.view.KeyEvent)

      เรียกใช้เพื่อประมวลผลการปล่อยปุ่มจริง เช่น เกมแพดหรือปุ่ม D-pad

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

KeyEvent
ออบเจ็กต์ที่อธิบายเหตุการณ์ของปุ่มบังคับทิศทาง (D-pad) และปุ่มของเกมแพด เหตุการณ์สําคัญจะมีคีย์โค้ดที่ระบุปุ่มที่ทริกเกอร์ เช่น DPAD_DOWN หรือ BUTTON_A คุณรับรหัสคีย์ได้โดยเรียกใช้ getKeyCode() หรือจากคอลแบ็กเหตุการณ์สําคัญ เช่น onKeyDown()
MotionEvent
ออบเจ็กต์ที่อธิบายอินพุตจากการเคลื่อนไหวของจอยสติ๊กและทริกเกอร์ไหล่ เหตุการณ์การเคลื่อนไหวจะมาพร้อมกับรหัสการดำเนินการและชุดค่าแกน โค้ดการดำเนินการจะระบุการเปลี่ยนแปลงสถานะที่เกิดขึ้น เช่น การขยับจอยสติ๊ก ค่าแกนอธิบายตำแหน่งและพร็อพเพอร์ตี้การเคลื่อนไหวอื่นๆ สำหรับการควบคุมทางกายภาพที่เฉพาะเจาะจง เช่น AXIS_X หรือ AXIS_RTRIGGER คุณรับรหัสการดำเนินการได้ด้วยการเรียกใช้ getAction() และรับค่าแกนด้วยการเรียกใช้ getAxisValue()

บทเรียนนี้เน้นวิธีจัดการอินพุตจากการควบคุมจริงที่ใช้กันมากที่สุด (ปุ่มเกมแพด ปุ่มบังคับทิศทาง และจอยสติ๊ก) ในหน้าจอเกมด้วยการใช้วิธีเรียกกลับและการประมวลผลออบเจ็กต์ KeyEvent และ MotionEvent ที่กล่าวไว้ข้างต้นView

ตรวจสอบว่าเชื่อมต่ออุปกรณ์ควบคุมเกมแล้ว

เมื่อรายงานเหตุการณ์อินพุต Android จะไม่แยกความแตกต่างระหว่างเหตุการณ์ที่มาจากอุปกรณ์ที่ไม่ใช่เกมคอนโทรลเลอร์กับเหตุการณ์ที่มาจากเกมคอนโทรลเลอร์ เช่น การทํางานบนหน้าจอสัมผัสจะสร้างเหตุการณ์ AXIS_X ที่แสดงพิกัด X ของพื้นผิวสัมผัส แต่จอยสติ๊กจะสร้างเหตุการณ์ AXIS_X ที่แสดงตําแหน่ง X ของจอยสติ๊ก หากเกมของคุณจัดการอินพุตจากตัวควบคุมเกม คุณควรตรวจสอบก่อนว่าเหตุการณ์อินพุตมาจากแหล่งที่มาประเภทที่เกี่ยวข้อง

หากต้องการตรวจสอบว่าอุปกรณ์อินพุตที่เชื่อมต่อเป็นอุปกรณ์ควบคุมเกม ให้เรียกใช้ getSources() เพื่อรับฟิลด์บิตแบบรวมของประเภทแหล่งที่มาของอินพุตที่อุปกรณ์นั้นรองรับ จากนั้นทดสอบเพื่อดูว่ามีการตั้งค่าช่องต่อไปนี้หรือไม่

  • ประเภทแหล่งที่มาของ SOURCE_GAMEPAD บ่งชี้ว่าอุปกรณ์อินพุตมีปุ่มเกมแพด (เช่น BUTTON_A) โปรดทราบว่าประเภทแหล่งที่มานี้ไม่ได้บ่งชี้อย่างแน่ชัดว่าเกมคอนโทรลเลอร์มีปุ่ม D-pad หรือไม่ แม้ว่าโดยทั่วไปแล้วเกมแพดส่วนใหญ่จะมีตัวควบคุมทิศทาง
  • ประเภทแหล่งที่มาของ SOURCE_DPAD บ่งบอกว่าอุปกรณ์อินพุตมีปุ่ม D-pad (เช่น DPAD_UP)
  • แหล่งที่มาประเภท SOURCE_JOYSTICK บ่งบอกว่าอุปกรณ์อินพุตมีแท่งควบคุมแบบอนาล็อก (เช่น จอยสติ๊กที่บันทึกการเคลื่อนไหวตาม AXIS_X และ AXIS_Y)

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

Kotlin

fun getGameControllerIds(): List<Int> {
    val gameControllerDeviceIds = mutableListOf<Int>()
    val deviceIds = InputDevice.getDeviceIds()
    deviceIds.forEach { deviceId ->
        InputDevice.getDevice(deviceId).apply {

            // Verify that the device has gamepad buttons, control sticks, or both.
            if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
                // This device is a game controller. Store its device ID.
                gameControllerDeviceIds
                        .takeIf { !it.contains(deviceId) }
                        ?.add(deviceId)
            }
        }
    }
    return gameControllerDeviceIds
}

Java

public ArrayList<Integer> getGameControllerIds() {
    ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
    int[] deviceIds = InputDevice.getDeviceIds();
    for (int deviceId : deviceIds) {
        InputDevice dev = InputDevice.getDevice(deviceId);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

นอกจากนี้ คุณอาจต้องตรวจสอบความสามารถของอินพุตแต่ละรายการที่อุปกรณ์ควบคุมเกมที่เชื่อมต่อรองรับ การทำเช่นนี้อาจเป็นประโยชน์ เช่น หากคุณต้องการให้เกมใช้เฉพาะอินพุตจากชุดตัวควบคุมทางกายภาพที่เกม เข้าใจเท่านั้น

หากต้องการตรวจหาว่าตัวควบคุมเกมที่เชื่อมต่ออยู่รองรับรหัสแป้นหรือรหัสแกนใดหรือไม่ ให้ใช้เทคนิคต่อไปนี้

  • ใน Android 4.4 (API ระดับ 19) ขึ้นไป คุณสามารถตรวจสอบว่าตัวควบคุมเกมที่เชื่อมต่ออยู่รองรับรหัสคีย์หรือไม่โดยเรียกใช้ hasKeys(int...)
  • ใน Android 3.1 (API ระดับ 12) ขึ้นไป คุณจะดูแกนทั้งหมดที่พร้อมใช้งานซึ่งรองรับในเกมคอนโทรลเลอร์ที่เชื่อมต่อได้โดยเรียกใช้ getMotionRanges() ก่อน จากนั้นเรียกใช้ getAxis() ในออบเจ็กต์ InputDevice.MotionRange แต่ละรายการที่แสดงผลเพื่อรับรหัสแกน

ประมวลผลการกดปุ่มเกมแพด

รูปที่ 1 แสดงวิธีที่ Android แมปโค้ดคีย์และค่าแกนกับตัวควบคุมทางกายภาพในตัวควบคุมเกมส่วนใหญ่

รูปที่ 1 โปรไฟล์สำหรับตัวควบคุมเกมทั่วไป

ไฮไลต์ในรูปอ้างอิงถึงข้อมูลต่อไปนี้

รหัสคีย์ทั่วไปที่สร้างขึ้นโดยการกดปุ่มเกมแพด ได้แก่ BUTTON_A, BUTTON_B, BUTTON_SELECT และ BUTTON_START คอนโทรลเลอร์เกมบางรุ่นจะทริกเกอร์รหัสแป้น DPAD_CENTER ด้วยเมื่อกดแถบกลางของปุ่มบังคับทิศทาง เกมสามารถตรวจสอบคีย์โค้ดได้โดยเรียกใช้ getKeyCode() หรือจากคอลแบ็กเหตุการณ์สำคัญ เช่น onKeyDown() และหากคีย์โค้ดแสดงถึงเหตุการณ์ที่เกี่ยวข้องกับเกม ให้ประมวลผลเป็นการดำเนินการในเกม ตารางที่ 1 แสดงการดําเนินการในเกมที่แนะนําสําหรับปุ่มเกมแพดที่ใช้กันมากที่สุด

ตารางที่ 1 การดำเนินการในเกมที่แนะนำสำหรับปุ่มของเกมแพด

การดำเนินการในเกม รหัสปุ่ม
เริ่มเกมในเมนูหลัก หรือหยุดเล่นชั่วคราว/ยกเลิกการหยุดชั่วคราวระหว่างเกม BUTTON_START*
แสดงเมนู BUTTON_SELECT* และ KEYCODE_MENU*
เช่นเดียวกับลักษณะการไปยังส่วนต่างๆ ของกลับของ Android ที่อธิบายไว้ในคู่มือการออกแบบการนำทาง KEYCODE_BACK
กลับไปที่รายการก่อนหน้าในเมนู BUTTON_B
ยืนยันการเลือกหรือดำเนินการหลักในเกม BUTTON_A และ DPAD_CENTER

* เกมไม่ควรใช้ปุ่มเริ่ม เลือก หรือเมนู

เคล็ดลับ: ลองใส่หน้าจอการกําหนดค่าในเกมเพื่อให้ผู้ใช้ปรับเปลี่ยนการแมปตัวควบคุมเกมของตนเองสําหรับการดําเนินการในเกม

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่คุณอาจลบล้าง onKeyDown() เพื่อเชื่อมโยงการกดปุ่ม BUTTON_A และ DPAD_CENTER กับการดำเนินการในเกม

Kotlin

class GameView(...) : View(...) {
    ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

Java

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

หมายเหตุ: ใน Android 4.2 (API ระดับ 17) และต่ำกว่า ระบบจะถือว่า BUTTON_A เป็นแป้นย้อนกลับของ Android โดยค่าเริ่มต้น หากแอปรองรับ Android เวอร์ชันเหล่านี้ โปรดตรวจสอบว่า BUTTON_A เป็นการดำเนินการหลักในเกม หากต้องการดูเวอร์ชัน Android SDK ปัจจุบันในอุปกรณ์ ให้ดูค่า Build.VERSION.SDK_INT

ประมวลผลการป้อนข้อมูลด้วยปุ่มบังคับทิศทาง

ปุ่มบังคับทิศทาง 4 ทิศทาง (D-pad) เป็นการควบคุมด้วยอุปกรณ์ทั่วไปในเกมคอนโทรลเลอร์หลายรุ่น Android จะรายงานการกดปุ่มขึ้นและลงของ D-pad เป็นเหตุการณ์ AXIS_HAT_Y ที่มีช่วงตั้งแต่ -1.0 (ขึ้น) ถึง 1.0 (ลง) และการกดปุ่มซ้ายหรือขวาของ D-pad เป็นเหตุการณ์ AXIS_HAT_Y ที่มีช่วงตั้งแต่ -1.0 (ซ้าย) ถึง 1.0 (ขวา)

คอนโทรลเลอร์บางรุ่นจะรายงานการกดปุ่ม D-pad ด้วยรหัสคีย์แทน หากเกมให้ความสำคัญกับการกด D-pad คุณควรถือว่าเหตุการณ์ของแกนหมวกและรหัสแป้น D-pad เป็นเหตุการณ์การป้อนข้อมูลเดียวกันตามที่อธิบายไว้ในตารางที่ 2

ตารางที่ 2 การดําเนินการเริ่มต้นที่แนะนําสําหรับเกมสำหรับรหัสแป้น D-pad และค่าแกน Hat

การดำเนินการในเกม รหัสแป้น D-pad รหัสแกนหมวก
ย้ายขึ้น KEYCODE_DPAD_UP AXIS_HAT_Y (สำหรับค่า 0 ถึง -1.0)
ย้ายลง KEYCODE_DPAD_DOWN AXIS_HAT_Y (สำหรับค่า 0 ถึง 1.0)
ย้ายไปทางซ้าย KEYCODE_DPAD_LEFT AXIS_HAT_X (สำหรับค่า 0 ถึง -1.0)
ย้ายไปทางขวา KEYCODE_DPAD_RIGHT AXIS_HAT_X (สำหรับค่า 0 ถึง 1.0)

ข้อมูลโค้ดต่อไปนี้แสดงคลาสตัวช่วยที่ช่วยให้คุณตรวจสอบค่าแกน Hat และค่ารหัสคีย์จากเหตุการณ์อินพุตเพื่อระบุทิศทางของปุ่ม D-pad

Kotlin

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD
    }
}

Java

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

คุณสามารถใช้คลาสตัวช่วยนี้ในเกมได้ทุกที่ที่ต้องการประมวลผลการป้อนข้อมูลด้วยปุ่มบังคับทิศทาง (เช่น ใน onGenericMotionEvent() หรือ onKeyDown() callback)

เช่น

Kotlin

private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if (Dpad.isDpadDevice(event)) {
        when (dpad.getDirectionPressed(event)) {
            Dpad.LEFT -> {
                // Do something for LEFT direction press
                ...
                return true
            }
            Dpad.RIGHT -> {
                // Do something for RIGHT direction press
                ...
                return true
            }
            Dpad.UP -> {
                // Do something for UP direction press
                ...
                return true
            }
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

Java

Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = dpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

ประมวลผลการเคลื่อนไหวของจอยสติ๊ก

เมื่อผู้เล่นขยับจอยสติ๊กบนตัวควบคุมเกม Android จะรายงาน MotionEvent ที่มีACTION_MOVEโค้ดการดำเนินการและตำแหน่งที่อัปเดตของแกนของจอยสติ๊ก เกมสามารถใช้ข้อมูลที่ได้จาก MotionEvent เพื่อระบุว่ามีการเคลื่อนที่ของจอยสติ๊กที่สนใจหรือไม่

โปรดทราบว่าเหตุการณ์การเคลื่อนไหวของจอยสติ๊กอาจจัดกลุ่มตัวอย่างการเคลื่อนไหวหลายรายการไว้ด้วยกันภายในออบเจ็กต์เดียว ออบเจ็กต์ MotionEvent มีตำแหน่งปัจจุบันสำหรับแกนจอยสติ๊กแต่ละแกน รวมถึงตำแหน่งที่ผ่านมาหลายตำแหน่งสำหรับแต่ละแกน เมื่อรายงานเหตุการณ์การเคลื่อนไหวด้วยรหัสการดำเนินการ ACTION_MOVE (เช่น การเคลื่อนไหวของจอยสติ๊ก) Android จะจัดกลุ่มค่าแกนเพื่อเพิ่มประสิทธิภาพ ค่าย้อนหลังสำหรับแกนประกอบด้วยชุดค่าที่แตกต่างกันซึ่งเก่ากว่าค่าแกนปัจจุบัน และใหม่กว่าค่าที่รายงานในเหตุการณ์การเคลื่อนไหวก่อนหน้า ดูรายละเอียดในข้อมูลอ้างอิง MotionEvent

คุณสามารถใช้ข้อมูลประวัติเพื่อให้แสดงผลการเคลื่อนที่ของวัตถุในเกม โดยอิงตามอินพุตของจอยสติ๊กได้แม่นยำยิ่งขึ้น หากต้องการเรียกข้อมูลค่าปัจจุบันและค่าที่ผ่านมา ให้เรียกใช้ getAxisValue() หรือ getHistoricalAxisValue() นอกจากนี้ยังดูจำนวนจุดในอดีตในเหตุการณ์จอยสติ๊กได้ด้วย โดยโทรไปที่ getHistorySize()

ข้อมูลโค้ดต่อไปนี้แสดงวิธีลบล้าง Callback onGenericMotionEvent() เพื่อประมวลผลอินพุตจอยสติ๊ก คุณควรประมวลผลค่าที่ผ่านมาสำหรับแกนก่อน จากนั้นจึงประมวลผลตำแหน่งปัจจุบันของแกน

Kotlin

class GameView(...) : View(...) {

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {

        // Check that the event came from a game controller
        return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                && event.action == MotionEvent.ACTION_MOVE) {

            // Process the movements starting from the
            // earliest historical position in the batch
            (0 until event.historySize).forEach { i ->
                // Process the event at historical position i
                processJoystickInput(event, i)
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1)
            true
        } else {
            super.onGenericMotionEvent(event)
        }
    }
}

Java

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

ก่อนใช้อินพุตจอยสติ๊ก คุณต้องตรวจสอบว่าจอยสติ๊กอยู่ในตำแหน่งกึ่งกลางหรือไม่ จากนั้นคำนวณการเคลื่อนไหวของแกนให้สอดคล้องกัน โดยปกติแล้ว จอยสติ๊กจะมีพื้นที่เรียบ ซึ่งก็คือช่วงของค่าที่อยู่ใกล้กับพิกัด (0,0) ที่ถือว่าแกนอยู่ตรงกลาง หากค่าแกนที่ Android รายงานอยู่ภายในพื้นที่ราบ คุณควรถือว่าตัวควบคุมอยู่ในสถานะหยุดนิ่ง (นั่นคือไม่มีการเคลื่อนไหวตามทั้ง 2 แกน)

ข้อมูลโค้ดด้านล่างแสดงวิธีตัวช่วยที่คำนวณการเคลื่อนที่ตามแกนแต่ละแกน คุณเรียกใช้ตัวช่วยนี้ในเมธอด processJoystickInput() ที่อธิบายไว้ด้านล่าง

Kotlin

private fun getCenteredAxis(
        event: MotionEvent,
        device: InputDevice,
        axis: Int,
        historyPos: Int
): Float {
    val range: InputDevice.MotionRange? = device.getMotionRange(axis, event.source)

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    range?.apply {
        val value: Float = if (historyPos < 0) {
            event.getAxisValue(axis)
        } else {
            event.getHistoricalAxisValue(axis, historyPos)
        }

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value
        }
    }
    return 0f
}

Java

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    if (range != null) {
        final float flat = range.getFlat();
        final float value =
                historyPos < 0 ? event.getAxisValue(axis):
                event.getHistoricalAxisValue(axis, historyPos);

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

เมื่อนำทุกอย่างมารวมเข้าด้วยกัน นี่คือวิธีที่คุณอาจใช้การเคลื่อนที่ของจอยสติ๊กในเกม

Kotlin

private fun processJoystickInput(event: MotionEvent, historyPos: Int) {

    val inputDevice = event.device

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    var x: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos)
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos)
    }
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos)
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    var y: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos)
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos)
    }
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos)
    }

    // Update the ship object based on the new x and y values
}

Java

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice inputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

หากต้องการรองรับตัวควบคุมเกมที่มีฟีเจอร์ที่ซับซ้อนกว่าจอยสติ๊กเดียว ให้ทําตามแนวทางปฏิบัติแนะนําต่อไปนี้

  • จับที่จับของคอนโทรลเลอร์แบบ 2 แท่ง อุปกรณ์ควบคุมเกมหลายรุ่นมีจอยสติ๊กทั้งซ้ายและขวา สำหรับแท่งควบคุมด้านซ้าย Android จะรายงานการเคลื่อนไหวในแนวนอนเป็นเหตุการณ์ AXIS_X และการเคลื่อนไหวในแนวตั้งเป็นเหตุการณ์ AXIS_Y สําหรับแท่งควบคุมด้านขวา Android จะรายงานการเคลื่อนไหวในแนวนอนเป็นเหตุการณ์ AXIS_Z และการเคลื่อนไหวในแนวตั้งเป็นเหตุการณ์ AXIS_RZ อย่าลืมจัดการแท่งควบคุมทั้ง 2 แท่งในโค้ด
  • จัดการการกดไกปืนที่ไหล่ (และตรวจสอบว่าเกมของคุณทำงานร่วมกับเหตุการณ์ AXIS_ และ KEYCODE_BUTTON_ ได้) คอนโทรลเลอร์บางรุ่นมีปุ่มทริกเกอร์ที่ไหล่ซ้ายและขวา เมื่อทริกเกอร์เหล่านี้ปรากฏขึ้น ระบบจะส่งเหตุการณ์ AXIS_*TRIGGER หรือ KEYCODE_BUTTON_*2 หรือทั้ง 2 อย่าง สำหรับทริกเกอร์ซ้ายจะเป็น AXIS_LTRIGGER และ KEYCODE_BUTTON_L2 สําหรับทริกเกอร์ขวาจะเป็น AXIS_RTRIGGER และ KEYCODE_BUTTON_R2 เหตุการณ์แกนจะเกิดขึ้นหากทริกเกอร์ปล่อยช่วงของค่าระหว่าง 0 ถึง 1 เท่านั้น และตัวควบคุมบางรายการที่มีเอาต์พุตแบบแอนะล็อกจะแสดงเหตุการณ์ปุ่มนอกเหนือจากเหตุการณ์แกน เกมต้องรองรับทั้งเหตุการณ์ AXIS_ และ KEYCODE_BUTTON_ จึงจะใช้งานร่วมกับตัวควบคุมเกมทั่วไปได้อยู่ แต่ควรใช้เหตุการณ์ที่เหมาะกับเกมเพลย์มากที่สุดหากตัวควบคุมรายงานทั้ง 2 เหตุการณ์ ใน Android 4.3 (API ระดับ 18) ขึ้นไป ตัวควบคุมที่สร้าง AXIS_LTRIGGER จะรายงานค่าที่เหมือนกันสำหรับแกน AXIS_BRAKE ด้วย เช่นเดียวกับ AXIS_RTRIGGER และ AXIS_GAS Android จะรายงานการกดทริกเกอร์แบบอนาล็อกทั้งหมดด้วยค่าที่แปลงเป็นมาตรฐานตั้งแต่ 0.0 (ปล่อย) ถึง 1.0 (กดจนสุด)
  • ลักษณะการทำงานและการรองรับบางอย่างอาจแตกต่างกันไปในสภาพแวดล้อมการจําลอง แพลตฟอร์มจำลอง เช่น Google Play Games อาจทำงานแตกต่างกันเล็กน้อยโดยขึ้นอยู่กับความสามารถของระบบปฏิบัติการโฮสต์ เช่น ตัวควบคุมบางตัวที่ส่งทั้งเหตุการณ์ AXIS_ และ KEYCODE_BUTTON_ จะส่งเฉพาะเหตุการณ์ AXIS_ และอาจไม่รองรับตัวควบคุมบางตัวเลย