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

คอนโทรลเลอร์มีการทำงาน 2 ประเภท ได้แก่

  • KeyEvent ใช้กับปุ่มที่มีสถานะไบนารีเป็น "เปิด" และ "ปิด"
  • MotionEvent ใช้กับแกนที่แสดงผลช่วงของค่า เช่น -1 ถึง 1 สำหรับ อนาล็อกสติ๊ก หรือ 0 ถึง 1 สำหรับทริกเกอร์แบบอนาล็อก

คุณอ่านข้อมูลเหล่านี้ได้จาก View ที่มี focus

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
  if (event.isFromSource(SOURCE_GAMEPAD)
      && event.repeatCount == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: $keyCode")
      return true
  }

  return super.onKeyDown(keyCode, event)
}

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: $event")
      return true
  }

  return super.onGenericMotionEvent(event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (event.isFromSource(SOURCE_GAMEPAD)
          && event.getRepeatCount() == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: " + keyCode);
      return true;
  }

  return super.onKeyDown(keyCode, event);
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: " + event);
      return true;
  }
  return super.onGenericMotionEvent(event);
}

หากจำเป็น คุณสามารถอ่านเหตุการณ์จาก Activity ได้โดยตรงแทน

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

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

หากต้องการยืนยันว่า InputDevice ที่เชื่อมต่อเป็นอุปกรณ์ควบคุมเกม ให้ใช้ฟังก์ชัน supportsSource(int)

  • ประเภทแหล่งที่มาของ SOURCE_GAMEPAD แสดงว่าอุปกรณ์อินพุตมีปุ่มควบคุม (เช่น KEYCODE_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 (supportsSource(SOURCE_GAMEPAD)
              || supportsSource(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);

      if (dev == null) {
          continue;
      }

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

อินพุตของตัวควบคุมกระบวนการ

ส่วนนี้อธิบายประเภทของเกมคอนโทรลเลอร์ที่รองรับใน Android

นักพัฒนาแอป C++ ควรใช้ไลบรารีตัวควบคุมเกม โดยจะรวมตัวควบคุมทั้งหมดเข้ากับชุดฟีเจอร์ย่อยที่ใช้กันมากที่สุด และมอบอินเทอร์เฟซที่สอดคล้องกันระหว่างตัวควบคุมเหล่านั้น ซึ่งรวมถึงความสามารถในการตรวจหาเลย์เอาต์ปุ่ม

รูปนี้แสดงให้เห็นว่านักพัฒนาเกม Android คาดหวังให้คอนโทรลเลอร์ทั่วไป มีลักษณะอย่างไรใน Android

ตัวควบคุมเกมทั่วไปที่มีอินพุตที่ติดป้ายกำกับ ซึ่งรวมถึง D-Pad, อะนาล็อกสติ๊ก และปุ่ม
รูปที่ 1 โปรไฟล์สำหรับเกมคอนโทรลเลอร์ทั่วไป

ตารางนี้แสดงชื่อและประเภทเหตุการณ์มาตรฐานสำหรับเกมคอนโทรลเลอร์ ดูรายการเหตุการณ์ทั้งหมดได้ที่ตัวแปรทั่วไป ระบบ ส่งเหตุการณ์ MotionEvent ผ่านเหตุการณ์ onGenericMotionEvent และเหตุการณ์ KeyEvent ผ่าน onKeyDown และ onKeyUp

อินพุตของเกมแพด KeyEvent MotionEvent
1. D-Pad
AXIS_HAT_X
(อินพุตแนวนอน)
AXIS_HAT_Y
(อินพุตแนวตั้ง)
2. ปุ่มอนาล็อกซ้าย
KEYCODE_BUTTON_THUMBL
(เมื่อกดเข้าไป)
AXIS_X
(การเคลื่อนไหวในแนวนอน)
AXIS_Y
(การเคลื่อนไหวในแนวตั้ง)
3. ปุ่มอนาล็อกขวา
KEYCODE_BUTTON_THUMBR
(เมื่อกดเข้าไป)
AXIS_Z
(การเคลื่อนไหวในแนวนอน)
AXIS_RZ
(การเคลื่อนไหวในแนวตั้ง)
4. ปุ่ม X KEYCODE_BUTTON_X
5. ปุ่ม A KEYCODE_BUTTON_A
6. ปุ่ม Y KEYCODE_BUTTON_Y
7. ปุ่ม B KEYCODE_BUTTON_B
8. ปุ่มกันชนขวา
KEYCODE_BUTTON_R1
9. ทริกเกอร์ขวา
AXIS_RTRIGGER
10. ทริกเกอร์ซ้าย AXIS_LTRIGGER
11. ปุ่มกันชนซ้าย KEYCODE_BUTTON_L1
12. เริ่ม KEYCODE_BUTTON_START
13. เลือก KEYCODE_BUTTON_SELECT

จัดการการกดปุ่ม

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

  • ตรวจสอบว่ากิจกรรมมาจาก SOURCE_GAMEPAD
  • ตรวจสอบว่าคุณได้รับปุ่มเพียงครั้งเดียวด้วย KeyEvent.getRepeatCount() Android จะส่งเหตุการณ์แป้นซ้ำเช่นเดียวกับที่คุณกดแป้นพิมพ์ค้างไว้
  • ระบุว่ามีการจัดการเหตุการณ์โดยการส่งคืน true
  • ส่งเหตุการณ์ที่ไม่ได้จัดการไปยัง super เพื่อยืนยันว่าเลเยอร์ความเข้ากันได้ต่างๆ ของ Android ทํางานได้อย่างเหมาะสม

    Kotlin

    class GameView : View {
    // ...
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        event.apply {
            var handled = false
    
            // make sure we're handling gamepad events
            if (isFromSource(SOURCE_GAMEPAD)) {
    
                // avoid processing the keycode repeatedly
                if (repeatCount == 0) {
                    when (keyCode) {
                        // handle the "A" button
                        KEYCODE_BUTTON_A -> {
                          handled = true
                        }
                    }
                    // ...
                }
            }
            if (handled) {
                return true
            }
       }
       return super.onKeyDown(keyCode, event)
      }
    }
    

    Java

    public class GameView extends View {
    // ...
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        // make sure we're handling gamepad events
        if (event.isFromSource(SOURCE_GAMEPAD)) {
            // avoid processing the keycode repeatedly
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    case KEYCODE_BUTTON_A:
                        // handle the "A" button
                        handled = true;
                        break;
                    // ...
                }
            }
            // mark this event as handled
            if (handled) {
                return true;
            }
        }
        // Always do this instead of "return false"
        // it allows Android's input compatibility layers to work
        return super.onKeyDown(keyCode, event);
      }
    }
    

ประมวลผลอินพุตปุ่มบังคับทิศทาง

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

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

ตารางที่ 2 การดำเนินการในเกมเริ่มต้นที่แนะนำสำหรับรหัสคีย์ของ D-pad และค่าแกนของหมวก

การกระทำในเกม รหัสคีย์ 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)

ข้อมูลโค้ดต่อไปนี้แสดงคลาส Helper ที่ช่วยให้คุณตรวจสอบค่าแกนหมวก และรหัสคีย์จากเหตุการณ์อินพุตเพื่อกำหนดทิศทางของ 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.
            return event.isFromSource(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.
        return event.isFromSource(InputDevice.SOURCE_DPAD);
     }
}

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

เช่น

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

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

คุณสามารถดึงค่าปัจจุบันและค่าในอดีตได้โดยใช้วิธีการต่อไปนี้

ข้อมูลโค้ดต่อไปนี้แสดงวิธีลบล้าง 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
}

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

  • ควบคุมแท่งควบคุมคู่ ตัวควบคุมเกมหลายตัวมีทั้งจอยสติ๊กซ้าย และขวา สำหรับก้านซ้าย 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_ และอาจไม่มีการรองรับคอนโทรลเลอร์บางตัวเลย

รูปแบบที่พบบ่อย

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

ซึ่งหมายความว่าคุณสามารถทดสอบด้วยอุปกรณ์ควบคุมเพียง 3 เครื่องในทีมพัฒนา และมั่นใจได้ว่าเกมของคุณเล่นได้โดยไม่ต้องใช้รายการที่อนุญาตและรายการที่ไม่อนุญาต

ประเภทรีโมตที่พบบ่อย

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

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

ประเภทรีโมต ความแตกต่างทางพฤติกรรม การติดฉลากรูปแบบต่างๆ
คอนโทรลเลอร์สไตล์ Xbox

โดยปกติแล้ว ตัวควบคุมเหล่านี้จะสร้างขึ้นสำหรับแพลตฟอร์ม Microsoft Xbox และ Windows*

ตัวควบคุมเหล่านี้ตรงกับชุดฟีเจอร์ที่ระบุไว้ในประมวลผลอินพุตของตัวควบคุม ปุ่ม L2/R2 บนตัวควบคุมเหล่านี้มีป้ายกำกับเป็น LT/RT
ตัวควบคุมสไตล์ Switch

โดยปกติแล้ว คอนโทรลเลอร์เหล่านี้ออกแบบมาสำหรับคอนโซลตระกูล Nintendo Switch*

ตัวควบคุมเหล่านี้จะส่ง KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent ปุ่ม L2/R2 บนตัวควบคุมเหล่านี้มีป้ายกำกับเป็น ZL/ZR

นอกจากนี้ คอนโทรลเลอร์เหล่านี้ยังสลับปุ่ม A กับ B และปุ่ม X กับ Y ด้วย ดังนั้น KEYCODE_BUTTON_A จึงเป็นปุ่มที่ติดป้ายกำกับว่า B และในทางกลับกัน

จอยบังคับสไตล์ PlayStation

โดยปกติแล้ว ตัวควบคุมเหล่านี้ออกแบบมาสำหรับคอนโซลตระกูล Sony PlayStation*

โดยตัวควบคุมเหล่านี้จะส่ง MotionEvent เช่น Xbox Style Controllers แต่ก็ส่ง KeyEvent เช่น Switch Style Controllers เมื่อกดจนสุดด้วย โดยคอนโทรลเลอร์เหล่านี้จะใช้ชุดสัญลักษณ์ที่แตกต่างกันสำหรับปุ่มบนใบหน้า

* Microsoft, Xbox และ Windows เป็นเครื่องหมายการค้าจดทะเบียนของ Microsoft Nintendo Switch เป็นเครื่องหมายการค้าจดทะเบียนของ Nintendo of America Inc. PlayStation เป็นเครื่องหมายการค้าจดทะเบียนของ Sony Interactive Entertainment Inc.

ปุ่มทริกเกอร์ที่แยกความแตกต่าง

คอนโทรลเลอร์บางรุ่นจะส่ง AXIS_LTRIGGER และ AXIS_RTRIGGER บางรุ่นจะส่ง KEYCODE_BUTTON_L2 และ KEYCODE_BUTTON_R2 และบางรุ่นจะส่งเหตุการณ์ทั้งหมดนี้ ตามความสามารถของฮาร์ดแวร์ เพิ่มความเข้ากันได้สูงสุดด้วยการรองรับเหตุการณ์ทั้งหมด เหล่านี้

ตัวควบคุมทั้งหมดที่ส่ง AXIS_LTRIGGER จะส่ง AXIS_BRAKE ด้วยเช่นกัน และในทำนองเดียวกัน AXIS_RTRIGGER และ AXIS_GAS จะช่วยเพิ่มความเข้ากันได้สูงสุดระหว่างพวงมาลัยแข่งกับตัวควบคุมเกมทั่วไป โดยทั่วไปแล้วการดำเนินการนี้จะไม่ทำให้เกิดปัญหา แต่โปรด ระมัดระวังสำหรับฟีเจอร์ต่างๆ เช่น หน้าจอการแมปแป้นพิมพ์ใหม่

ทริกเกอร์ MotionEvent KeyEvent
ทริกเกอร์ซ้าย AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
ทริกเกอร์ขวา AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

คุณควรตรวจสอบว่าเกมรองรับทั้ง KeyEvent และ MotionEvent เพื่อรักษาความเข้ากันได้กับคอนโทรลเลอร์ให้ได้มากที่สุด และตรวจสอบว่าระบบได้นำเหตุการณ์ที่ซ้ำกันออกแล้ว

คอนโทรลเลอร์ที่รองรับ

เมื่อทดสอบ เราขอแนะนำให้คุณตรวจสอบว่าเกมทำงานร่วมกับคอนโทรลเลอร์ 1 ตัว ในแต่ละหมวดหมู่ได้

  • สไตล์ Xbox
  • สไตล์ Nintendo Switch
  • สไตล์ PlayStation

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