Xử lý thao tác của bộ điều khiển

Bộ điều khiển có hai loại thao tác:

  • KeyEvent được dùng cho mọi nút có trạng thái nhị phân là "bật" và "tắt"
  • MotionEvent được dùng cho mọi trục trả về một dải giá trị. Chẳng hạn như -1 đến 1 cho cần điều khiển tương tự hoặc 0 đến 1 cho cò tương tự.

Bạn có thể đọc các dữ liệu đầu vào này từ Viewfocus.

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);
}

Nếu cần, bạn có thể đọc trực tiếp các sự kiện từ Activity.

Xác minh rằng bộ điều khiển trò chơi đã được kết nối

Khi báo cáo các sự kiện đầu vào, Android sẽ dùng lại cùng một khoá hoặc mã nhận dạng trục cho các loại thiết bị đầu vào khác nhau. Ví dụ: thao tác trên màn hình cảm ứng sẽ tạo ra sự kiện AXIS_X biểu thị toạ độ X của bề mặt cảm ứng, nhưng tay cầm chơi game sẽ tạo ra sự kiện AXIS_X biểu thị vị trí X của cần điều khiển bên trái. Điều này có nghĩa là bạn phải kiểm tra loại nguồn để diễn giải đúng các sự kiện đầu vào.

Để xác minh rằng InputDevice đã kết nối là tay điều khiển trò chơi, hãy sử dụng hàm supportsSource(int):

  • Loại nguồn SOURCE_GAMEPAD cho biết thiết bị đầu vào có các nút trên tay điều khiển (ví dụ: KEYCODE_BUTTON_A). Xin lưu ý rằng loại nguồn này không cho biết chính xác liệu tay điều khiển trò chơi có các nút trên D-pad hay không, mặc dù hầu hết các tay điều khiển thường có các chế độ điều khiển hướng.
  • Loại nguồn SOURCE_DPAD cho biết thiết bị đầu vào có các nút D-pad (ví dụ: DPAD_UP).
  • Loại nguồn SOURCE_JOYSTICK cho biết thiết bị đầu vào có cần điều khiển tương tự (ví dụ: cần điều khiển ghi lại các chuyển động dọc theo AXIS_XAXIS_Y).

Đoạn mã sau đây cho thấy một phương thức trợ giúp cho phép bạn kiểm tra xem các thiết bị đầu vào đã kết nối có phải là bộ điều khiển trò chơi hay không. Nếu có, phương thức này sẽ truy xuất mã nhận dạng thiết bị cho tay điều khiển trò chơi. Sau đó, bạn có thể liên kết từng mã nhận dạng thiết bị với một người chơi trong trò chơi của mình và xử lý riêng các thao tác trong trò chơi cho từng người chơi đã kết nối. Để tìm hiểu thêm về cách hỗ trợ nhiều tay điều khiển trò chơi được kết nối đồng thời trên cùng một thiết bị chạy Android, hãy xem phần Hỗ trợ nhiều tay điều khiển trò chơi.

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;
}

Xử lý dữ liệu đầu vào của bộ điều khiển

Phần này mô tả các loại tay điều khiển trò chơi được hỗ trợ trên Android.

Nhà phát triển C++ nên sử dụng Thư viện Tay điều khiển trò chơi. Nó hợp nhất tất cả các bộ điều khiển thành nhóm con phổ biến nhất của các tính năng và cung cấp một giao diện nhất quán giữa các bộ điều khiển, bao gồm cả khả năng phát hiện bố cục nút.

Hình này cho thấy hình dạng của một bộ điều khiển thông thường mà nhà phát triển trò chơi Android có thể thấy trên Android.

Tay cầm chơi game chung có các đầu vào được gắn nhãn, bao gồm cả D-Pad, gậy điều khiển analog và các nút
Hình 1. Hồ sơ cho tay điều khiển trò chơi chung.

Bảng này liệt kê các tên và loại sự kiện chuẩn cho tay điều khiển trò chơi. Để xem danh sách đầy đủ các sự kiện, hãy xem phần Các biến thể phổ biến. Hệ thống gửi các sự kiện MotionEvent thông qua các sự kiện onGenericMotionEventKeyEvent thông qua onKeyDownonKeyUp.

Thông tin đầu vào của bộ điều khiển KeyEvent MotionEvent
1. D-Pad
AXIS_HAT_X
(đầu vào ngang)
AXIS_HAT_Y
(đầu vào dọc)
2. Cần điều khiển tương tự bên trái
KEYCODE_BUTTON_THUMBL
(khi nhấn vào)
AXIS_X
(chuyển động ngang)
AXIS_Y
(chuyển động dọc)
3. Cần điều khiển tương tự bên phải
KEYCODE_BUTTON_THUMBR
(khi nhấn vào)
AXIS_Z
(chuyển động ngang)
AXIS_RZ
(chuyển động dọc)
4. Nút X KEYCODE_BUTTON_X
5. Nút A KEYCODE_BUTTON_A
6. Nút Y KEYCODE_BUTTON_Y
7. Nút B KEYCODE_BUTTON_B
8. Nút Right Bumper
KEYCODE_BUTTON_R1
9. Nút cò bên phải
AXIS_RTRIGGER
10. Nút kích hoạt bên trái AXIS_LTRIGGER
11. Nút L1 KEYCODE_BUTTON_L1
12. Bắt đầu KEYCODE_BUTTON_START
13. Chọn KEYCODE_BUTTON_SELECT

Xử lý thao tác nhấn nút

Vì Android báo cáo các lần nhấn nút trên bộ điều khiển giống như các lần nhấn nút trên bàn phím, nên bạn cần:

  • Xác thực rằng sự kiện đến từ một SOURCE_GAMEPAD.
  • Đảm bảo bạn chỉ nhận được nút một lần bằng KeyEvent.getRepeatCount(), Android sẽ gửi các sự kiện lặp lại của phím giống như khi bạn nhấn và giữ một phím trên bàn phím.
  • Cho biết rằng một sự kiện được xử lý bằng cách trả về true.
  • Truyền các sự kiện chưa được xử lý đến super để xác minh rằng các lớp tương thích khác nhau của Android hoạt động phù hợp.

    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);
      }
    }
    

Xử lý dữ liệu đầu vào của bàn phím di chuyển

Bàn phím di chuyển 4 hướng (D-pad) là một nút điều khiển vật lý phổ biến trong nhiều tay điều khiển trò chơi. Android báo cáo các lần nhấn D-pad LÊN và XUỐNG dưới dạng sự kiện AXIS_HAT_Y, trong đó -1.0 cho biết hướng lên và 1.0 cho biết hướng xuống. Nó báo cáo các lần nhấn D-pad LEFT hoặc RIGHT dưới dạng sự kiện AXIS_HAT_X, trong đó -1.0 cho biết bên trái và 1.0 cho biết bên phải.

Thay vào đó, một số bộ điều khiển báo cáo các lần nhấn D-pad bằng mã khoá. Nếu trò chơi của bạn quan tâm đến các lần nhấn phím điều hướng, bạn nên coi các sự kiện trục mũ và mã khoá phím điều hướng là cùng một sự kiện đầu vào, như được đề xuất trong bảng 2.

Bảng 2. Các thao tác mặc định được đề xuất cho trò chơi đối với mã khoá D-pad và giá trị trục mũ.

Game Action Mã khoá D-pad Mã trục mũ
Di chuyển lên KEYCODE_DPAD_UP AXIS_HAT_Y (đối với các giá trị từ 0 đến -1,0)
Di chuyển xuống KEYCODE_DPAD_DOWN AXIS_HAT_Y (đối với các giá trị từ 0 đến 1)
Di chuyển sang trái KEYCODE_DPAD_LEFT AXIS_HAT_X (đối với các giá trị từ 0 đến -1,0)
Di chuyển sang phải KEYCODE_DPAD_RIGHT AXIS_HAT_X (đối với các giá trị từ 0 đến 1)

Đoạn mã sau đây cho thấy một lớp trợ giúp cho phép bạn kiểm tra các giá trị mã khoá và trục mũ từ một sự kiện đầu vào để xác định hướng của 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);
     }
}

Bạn có thể dùng lớp trợ giúp này trong trò chơi của mình bất cứ khi nào bạn muốn xử lý dữ liệu đầu vào của D-pad (ví dụ: trong các lệnh gọi lại onGenericMotionEvent() hoặc onKeyDown()).

Ví dụ:

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.
    ...
}

Xử lý chuyển động của cần điều khiển

Khi người chơi di chuyển cần điều khiển trên bộ điều khiển trò chơi, Android sẽ báo cáo một MotionEvent chứa mã thao tác ACTION_MOVE và vị trí mới của các trục cần điều khiển. Trò chơi của bạn có thể sử dụng dữ liệu do MotionEvent cung cấp để xác định xem có chuyển động nào của cần điều khiển mà trò chơi quan tâm hay không.

Xin lưu ý rằng các sự kiện chuyển động của cần điều khiển có thể kết hợp nhiều mẫu chuyển động với nhau trong một đối tượng duy nhất. Đối tượng MotionEvent chứa vị trí hiện tại cho từng trục cần điều khiển cũng như nhiều vị trí trước đây cho từng trục. Khi báo cáo các sự kiện chuyển động bằng mã thao tác ACTION_MOVE (chẳng hạn như chuyển động của cần điều khiển), Android sẽ gộp các giá trị trục để tăng hiệu quả. Các giá trị trước đây cho một trục bao gồm tập hợp các giá trị riêng biệt cũ hơn giá trị trục hiện tại và gần đây hơn các giá trị được báo cáo trong mọi sự kiện chuyển động trước đó. Hãy xem tài liệu tham khảo MotionEvent để biết thông tin chi tiết.

Để hiển thị chính xác chuyển động của một đối tượng trong trò chơi dựa trên đầu vào của cần điều khiển, bạn có thể sử dụng thông tin trong quá khứ do các đối tượng MotionEvent cung cấp.

Bạn có thể truy xuất các giá trị hiện tại và trước đây bằng các phương thức sau:

Đoạn mã sau đây cho thấy cách bạn có thể ghi đè phương thức gọi lại onGenericMotionEvent() để xử lý dữ liệu đầu vào của cần điều khiển. Trước tiên, bạn nên xử lý các giá trị trước đây cho một trục, sau đó xử lý vị trí hiện tại của trục đó.

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);
    }
}

Trước khi sử dụng đầu vào của cần điều khiển, bạn cần xác định xem cần điều khiển có được đặt ở vị trí trung tâm hay không, sau đó tính toán các chuyển động của trục tương ứng. Cần điều khiển thường có một vùng phẳng, tức là một dải giá trị gần toạ độ (0,0) mà tại đó trục được coi là ở vị trí trung tâm. Nếu giá trị trục do Android báo cáo nằm trong vùng phẳng, bạn nên coi bộ điều khiển ở trạng thái nghỉ (tức là không chuyển động dọc theo cả hai trục).

Đoạn mã này cho thấy một phương thức trợ giúp tính toán chuyển động dọc theo từng trục. Bạn gọi trình trợ giúp này trong phương thức processJoystickInput() được mô tả thêm trong mẫu sau:

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;
}

Tổng hợp lại, đây là cách bạn có thể xử lý chuyển động của cần điều khiển trong trò chơi:

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
}

Để hỗ trợ những tay điều khiển trò chơi có các tính năng phức tạp hơn ngoài một cần điều khiển duy nhất, hãy làm theo các phương pháp hay nhất sau:

  • Xử lý hai cần điều khiển. Nhiều tay điều khiển trò chơi có cả cần điều khiển bên trái và bên phải. Đối với cần điều khiển bên trái, Android báo cáo các chuyển động ngang dưới dạng sự kiện AXIS_X và các chuyển động dọc dưới dạng sự kiện AXIS_Y. Đối với cần điều khiển bên phải, Android báo cáo các chuyển động ngang dưới dạng sự kiện AXIS_Z và các chuyển động dọc dưới dạng sự kiện AXIS_RZ. Đảm bảo xử lý cả hai cần điều khiển trong mã của bạn.
  • Xử lý các lần nhấn nút kích hoạt ở vai (và đảm bảo trò chơi của bạn hoạt động với các sự kiện AXIS_KEYCODE_BUTTON_). Một số bộ điều khiển có nút kích hoạt ở vai trái và vai phải. Khi có các trình kích hoạt này, chúng sẽ phát ra sự kiện AXIS_*TRIGGER hoặc KEYCODE_BUTTON_*2 hoặc cả hai. Đối với cò súng bên trái, đây sẽ là AXIS_LTRIGGERKEYCODE_BUTTON_L2. Đối với cò phải, đây sẽ là AXIS_RTRIGGERKEYCODE_BUTTON_R2. Sự kiện trục chỉ xảy ra nếu bộ kích hoạt phát ra một dải giá trị từ 0 đến 1 và một số bộ điều khiển có đầu ra tương tự sẽ phát ra các sự kiện nút ngoài các sự kiện trục. Các trò chơi phải hỗ trợ cả sự kiện AXIS_KEYCODE_BUTTON_ để duy trì khả năng tương thích với tất cả các tay điều khiển trò chơi thông thường, nhưng nên ưu tiên sự kiện phù hợp nhất với lối chơi của bạn nếu một tay điều khiển báo cáo cả hai. Trên Android 4.3 (API cấp 18) trở lên, bộ điều khiển tạo ra AXIS_LTRIGGER cũng báo cáo một giá trị giống hệt cho trục AXIS_BRAKE. Điều này cũng đúng đối với AXIS_RTRIGGERAXIS_GAS. Android báo cáo tất cả các lần nhấn nút kích hoạt tương tự bằng một giá trị được chuẩn hoá từ 0,0 (nhả) đến 1,0 (nhấn hoàn toàn).
  • Hành vi và khả năng hỗ trợ cụ thể có thể khác nhau trong môi trường mô phỏng. Các nền tảng mô phỏng, chẳng hạn như Google Play Games, có thể có hành vi khác biệt đôi chút dựa trên các chức năng của hệ điều hành máy chủ. Ví dụ: một số bộ điều khiển phát cả sự kiện AXIS_KEYCODE_BUTTON_ chỉ phát sự kiện AXIS_ và có thể hoàn toàn không hỗ trợ một số bộ điều khiển.

Các biến thể phổ biến

Vì Android hỗ trợ nhiều loại tay điều khiển, nên có thể bạn sẽ không biết cách tạo và kiểm thử để xác minh rằng trò chơi của bạn hoạt động mà không có lỗi trong cơ sở người chơi. Chúng tôi nhận thấy rằng mặc dù có vẻ đa dạng, nhưng các nhà sản xuất bộ điều khiển trên khắp thế giới thường tuân thủ nhất quán 3 kiểu bộ điều khiển khác nhau. Một số thiết bị cung cấp các nút bật/tắt phần cứng giữa hai hoặc nhiều chế độ trong số này.

Điều này có nghĩa là bạn có thể kiểm thử chỉ với 3 bộ điều khiển trong nhóm phát triển và vẫn tự tin rằng trò chơi của bạn có thể chơi được mà không cần dùng đến danh sách cho phép và danh sách từ chối.

Các loại tay điều khiển phổ biến

Kiểu tay cầm phổ biến nhất thường mô phỏng bố cục của các máy chơi trò chơi phổ biến. Điều này vừa mang tính thẩm mỹ trong bố cục và nhãn nút, vừa mang tính chức năng theo những sự kiện được tạo ra. Bộ điều khiển có các nút bật/tắt phần cứng giữa các loại bảng điều khiển khác nhau sẽ thay đổi các sự kiện mà chúng gửi và thường là cả bố cục nút logic.

Khi kiểm thử, bạn nên xác thực rằng trò chơi của bạn hoạt động với một bộ điều khiển trong mỗi danh mục. Bạn có thể chọn kiểm thử bằng bộ điều khiển bên thứ nhất hoặc các nhà sản xuất bên thứ ba phổ biến. Nhìn chung, chúng tôi sẽ cố gắng hết sức để liên kết các bộ điều khiển phổ biến nhất với định nghĩa ở trên.

Loại bộ điều khiển Sự khác biệt về hành vi Biến thể về việc gắn nhãn
Tay cầm theo kiểu Xbox

Đây là những bộ điều khiển thường được sản xuất cho nền tảng Microsoft Xbox và Windows*.

Các bộ điều khiển này khớp với bộ tính năng được nêu trong phần Xử lý dữ liệu đầu vào của bộ điều khiển Các nút L2/R2 trên những bộ điều khiển này được gắn nhãn LT/RT
Bộ điều khiển kiểu công tắc

Những tay cầm này thường được thiết kế cho dòng máy chơi trò chơi Nintendo Switch*.

Các bộ điều khiển này gửi KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent Các nút L2/R2 trên những bộ điều khiển này được gắn nhãn ZL/ZR.

Các bộ điều khiển này cũng đổi nút AB, cũng như nút XY, nên KEYCODE_BUTTON_A là nút có nhãn B và ngược lại.

Tay cầm kiểu PlayStation

Những bộ điều khiển này thường được thiết kế cho dòng máy chơi trò chơi Sony PlayStation*.

Các bộ điều khiển này gửi MotionEvent, chẳng hạn như Bộ điều khiển kiểu Xbox, nhưng cũng gửi KeyEvent, chẳng hạn như Bộ điều khiển kiểu Switch khi được nhấn hoàn toàn. Các bộ điều khiển này sử dụng một bộ ký tự khác cho các nút trên mặt.

* Microsoft, Xbox và Windows là các nhãn hiệu đã đăng ký của Microsoft; Nintendo Switch là nhãn hiệu đã đăng ký của Nintendo of America Inc.; PlayStation là nhãn hiệu đã đăng ký của Sony Interactive Entertainment Inc.

Phân biệt các nút kích hoạt

Một số bộ điều khiển gửi AXIS_LTRIGGERAXIS_RTRIGGER, một số gửi KEYCODE_BUTTON_L2KEYCODE_BUTTON_R2, còn những bộ điều khiển khác gửi tất cả các sự kiện này dựa trên khả năng phần cứng của chúng. Tối đa hoá khả năng tương thích bằng cách hỗ trợ tất cả các sự kiện này.

Tất cả tay điều khiển gửi AXIS_LTRIGGER cũng sẽ gửi AXIS_BRAKE, tương tự đối với AXIS_RTRIGGERAXIS_GAS để giúp tối đa hoá khả năng tương thích giữa vô lăng đua xe và tay điều khiển trò chơi thông thường. Nhìn chung, điều này sẽ không gây ra vấn đề, nhưng hãy lưu ý đối với các tính năng như màn hình gán lại khoá.

Trigger MotionEvent KeyEvent
Nút kích hoạt bên trái AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Nút cò bên phải AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

Bạn nên cẩn thận xác minh rằng trò chơi của bạn có thể xử lý cả KeyEventMotionEvent để duy trì khả năng tương thích với nhiều tay điều khiển nhất có thể và các sự kiện không bị trùng lặp.

Bộ điều khiển được hỗ trợ

Khi kiểm thử, bạn nên xác thực rằng trò chơi của bạn hoạt động với một bộ điều khiển trong mỗi danh mục.

  • Kiểu Xbox
  • Kiểu Nintendo Switch
  • Phong cách PlayStation

Bạn có thể kiểm thử bằng bộ điều khiển của bên thứ nhất hoặc các nhà sản xuất bên thứ ba phổ biến. Nhìn chung, chúng tôi sẽ liên kết các bộ điều khiển phổ biến nhất với định nghĩa một cách chặt chẽ nhất có thể.