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

Ở cấp hệ thống, Android báo cáo mã sự kiện đầu vào từ tay điều khiển trò chơi dưới dạng mã phím và giá trị trục Android. Trong trò chơi, bạn có thể nhận được các mã và giá trị này, cũng như chuyển đổi chúng thành các hành động cụ thể trong trò chơi.

Khi người chơi kết nối vật lý hoặc ghép nối không dây tay điều khiển trò chơi với các thiết bị chạy Android, hệ thống sẽ tự động phát hiện tay điều khiển đó dưới dạng thiết bị đầu vào và bắt đầu báo cáo các sự kiện đầu vào. Trò chơi của bạn có thể nhận được các sự kiện đầu vào này bằng cách triển khai các phương thức gọi lại sau trong Activity đang hoạt động hoặc View tập trung (bạn nên triển khai lệnh gọi lại cho Activity hoặc View, nhưng không nên triển khai cả hai):

Bạn nên ghi lại các sự kiện từ đối tượng View cụ thể mà người dùng tương tác. Hãy kiểm tra các đối tượng sau do các lệnh gọi lại cung cấp để lấy thông tin về loại sự kiện đầu vào đã nhận được:

KeyEvent
Một đối tượng mô tả các sự kiện của bàn phím di chuyển (D-pad) và nút trên tay điều khiển trò chơi. Các sự kiện chính đi kèm với một mã phím cho biết nút cụ thể được kích hoạt, chẳng hạn như DPAD_DOWN hoặc BUTTON_A. Bạn có thể lấy mã khoá bằng cách gọi getKeyCode() hoặc từ các lệnh gọi lại sự kiện chính như onKeyDown().
MotionEvent
Một đối tượng mô tả dữ liệu đầu vào từ cần điều khiển và chuyển động của kích hoạt vai. Các sự kiện chuyển động đi kèm với mã hành động và một tập hợp giá trị trục. Mã hành động chỉ định sự thay đổi trạng thái đã xảy ra, chẳng hạn như cần điều khiển đang di chuyển. Các giá trị trục mô tả vị trí và các thuộc tính chuyển động khác của một thành phần điều khiển vật lý cụ thể, chẳng hạn như AXIS_X hoặc AXIS_RTRIGGER. Bạn có thể lấy mã thao tác bằng cách gọi getAction() và giá trị trục bằng cách gọi getAxisValue().

Bài học này sẽ tập trung vào cách bạn có thể xử lý dữ liệu đầu vào qua các loại nút điều khiển thực phổ biến nhất (các nút trên tay điều khiển trò chơi, bàn phím di chuyển và cần điều khiển) trên màn hình trò chơi bằng cách triển khai các phương thức gọi lại View nêu trên, đồng thời xử lý các đối tượng KeyEventMotionEvent.

Xác minh rằng tay đ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 không phân biệt giữa những sự kiện đến từ một thiết bị không phải tay điều khiển trò chơi và các sự kiện đến từ một tay điều khiển trò chơi. Ví dụ: một thao tác trên màn hình cảm ứng sẽ tạo ra một sự kiện AXIS_X biểu thị toạ độ X của bề mặt cảm ứng, nhưng cần điều khiển tạo ra một sự kiện AXIS_X biểu thị vị trí X của cần điều khiển. Nếu trò chơi của bạn quan tâm đến việc xử lý dữ liệu đầu vào của tay điều khiển trò chơi, thì trước tiên, bạn nên kiểm tra để đảm bảo sự kiện đầu vào đến từ một loại nguồn có liên quan.

Để xác minh rằng thiết bị đầu vào đã kết nối là tay điều khiển trò chơi, hãy gọi getSources() để lấy trường bit kết hợp của các loại nguồn đầu vào được hỗ trợ trên thiết bị đó. Sau đó, bạn có thể kiểm thử xem các trường sau đây đã được đặt hay chưa:

  • 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 trò chơi (ví dụ: BUTTON_A). Xin lưu ý rằng loại nguồn này không chỉ rõ tay điều khiển trò chơi có các nút D-pad hay không, mặc dù hầu hết các tay điều khiển trò chơi thường có nút đ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ác cần điều khiển analog (ví dụ: cần điều khiển ghi lại 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 thiết bị đầu vào đã kết nối có phải là tay điều khiển trò chơi hay không. Nếu có, phương thức này sẽ truy xuất mã thiết bị cho tay điều khiển trò chơi. Sau đó, bạn có thể liên kết từng mã thiết bị với người chơi trong trò chơi và xử lý riêng các hành động 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ị 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 (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;
}

Ngoài ra, bạn nên kiểm tra các chức năng đầu vào riêng lẻ được hỗ trợ trên tay điều khiển trò chơi đã kết nối. Điều này có thể hữu ích, chẳng hạn như nếu bạn muốn trò chơi chỉ sử dụng dữ liệu đầu vào từ tập hợp các chế độ điều khiển thực mà nó hiểu được.

Để phát hiện xem một tay điều khiển trò chơi đã kết nối có hỗ trợ mã phím hoặc mã trục cụ thể hay không, hãy sử dụng các kỹ thuật sau:

  • Trong Android 4.4 (API cấp 19) trở lên, bạn có thể xác định xem mã phím có được hỗ trợ trên tay điều khiển trò chơi đã kết nối hay không bằng cách gọi hasKeys(int...).
  • Trong Android 3.1 (API cấp 12) trở lên, bạn có thể tìm thấy tất cả các trục có sẵn được hỗ trợ trên tay điều khiển trò chơi đã kết nối bằng cách gọi getMotionRanges() trước. Sau đó, trên mỗi đối tượng InputDevice.MotionRange được trả về, hãy gọi getAxis() để lấy mã nhận dạng trục của đối tượng đó.

Xử lý các thao tác nhấn nút trên tay điều khiển trò chơi

Hình 1 cho thấy cách Android ánh xạ mã phím và giá trị trục với các nút điều khiển vật lý trên hầu hết tay điều khiển trò chơi.

Hình 1. Hồ sơ cho tay điều khiển trò chơi chung.

Các chú thích trong hình minh hoạ đề cập đến những chú thích sau:

Các mã phím phổ biến được tạo ra bằng các thao tác nhấn nút trên tay điều khiển trò chơi bao gồm BUTTON_A, BUTTON_B, BUTTON_SELECTBUTTON_START. Một số tay điều khiển trò chơi cũng kích hoạt mã phím DPAD_CENTER khi nhấn vào giữa thanh ngang D-pad. Trò chơi của bạn có thể kiểm tra mã khoá bằng cách gọi getKeyCode() hoặc từ các lệnh gọi lại sự kiện chính như onKeyDown(). Nếu sự kiện đó đại diện cho một sự kiện có liên quan đến trò chơi, hãy xử lý sự kiện đó dưới dạng một hành động trong trò chơi. Bảng 1 liệt kê các hành động trong trò chơi được đề xuất cho các nút phổ biến nhất trên tay điều khiển trò chơi.

Bảng 1. Hành động trò chơi nên dùng cho các nút trên tay điều khiển trò chơi.

Hành động trong trò chơi Mã phím của nút
Bắt đầu trò chơi trong trình đơn chính hoặc tạm dừng/huỷ tạm dừng trong khi chơi BUTTON_START*
Hiện trình đơn BUTTON_SELECT*KEYCODE_MENU*
Giống như hành vi điều hướng Quay lại của Android được mô tả trong hướng dẫn thiết kế Điều hướng. KEYCODE_BACK
Quay lại mục trước trong trình đơn BUTTON_B
Xác nhận lựa chọn hoặc thực hiện hành động chính trong trò chơi BUTTON_ADPAD_CENTER

* Trò chơi của bạn không nên phụ thuộc vào sự hiện diện của các nút Bắt đầu, Chọn hoặc Trình đơn.

Mẹo: Hãy cân nhắc cung cấp màn hình cấu hình trong trò chơi của bạn để cho phép người dùng cá nhân hoá sơ đồ ánh xạ tay điều khiển trò chơi của riêng họ cho các hành động trong trò chơi.

Đoạn mã sau cho biết cách bạn có thể ghi đè onKeyDown() để liên kết thao tác nhấn nút BUTTON_ADPAD_CENTER với một hành động trong trò chơi.

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

Lưu ý: Trên Android 4.2 (API cấp 17) trở xuống, theo mặc định, hệ thống coi BUTTON_A là phím Quay lại của Android. Nếu ứng dụng của bạn hỗ trợ các phiên bản Android này, hãy nhớ coi BUTTON_A là hành động chính trong trò chơi. Để xác định phiên bản SDK Android hiện tại trên thiết bị, hãy tham khảo giá trị Build.VERSION.SDK_INT.

Xử lý phương thức nhập bằng bàn phím di chuyển

Bàn phím di chuyển 4 chiều (D-pad) là một chế độ đ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 thao tác nhấn D-pad LÊN và XUỐNG dưới dạng các sự kiện AXIS_HAT_Y với phạm vi từ -1.0 (lên) đến 1.0 (xuống) và D-pad TRÁI hoặc PHẢI nhấn dưới dạng sự kiện AXIS_HAT_X với phạm vi từ -1.0 (trái) đến 1.0 (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ã phím. Nếu trò chơi của bạn quan tâm đến các thao tác nhấn D-pad, thì bạn nên coi các sự kiện trục mũ và mã phím D-pad là các sự kiện đầu vào giống nhau, như đề xuất trong bảng 2.

Bảng 2. Các hành động trò chơi mặc định nên dùng cho mã phím D-pad và giá trị trục mũ.

Hành động trong trò chơi Mã phím D-pad Mã trục mũ
Di chuyển lên KEYCODE_DPAD_UP AXIS_HAT_Y (cho các giá trị từ 0 đến -1)
Di chuyển xuống KEYCODE_DPAD_DOWN AXIS_HAT_Y (cho các giá trị từ 0 đến 1)
Di chuyển sang trái KEYCODE_DPAD_LEFT AXIS_HAT_X (cho các giá trị từ 0 đến -1)
Di chuyển sang phải KEYCODE_DPAD_RIGHT AXIS_HAT_X (cho 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 trục mũ và giá trị mã phím từ một sự kiện đầu vào để xác định hướng 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;
         }
     }
}

Bạn có thể sử dụng lớp trợ giúp này trong trò chơi ở bất cứ nơi nào bạn muốn xử lý phương thức nhập bằng 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 tay điều khiển trò chơi, Android sẽ báo cáo một MotionEvent chứa mã hành động ACTION_MOVE và vị trí cập nhật của trục của 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 chuyển động của cần điều khiển mà nó quan tâm có xảy ra 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ể gộp nhiều mẫu chuyển động với nhau trong một đối tượng. Đối tượng MotionEvent chứa vị trí hiện tại cho mỗi trục của cần điều khiển cũng như nhiều vị trí trước đây cho mỗi trục. Khi báo cáo các sự kiện chuyển động bằng mã hành động ACTION_MOVE (chẳng hạn như chuyển động của cần điều khiển), Android sẽ sắp xếp các giá trị trục để đảm bảo tính hiệu quả. Giá trị lịch sử 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 bất kỳ sự kiện chuyển động nào trước đó. Hãy xem tài liệu tham khảo MotionEvent để biết thông tin chi tiết.

Bạn có thể sử dụng thông tin trước đây để kết xuất chuyển động của một đối tượng trò chơi một cách chính xác hơn dựa trên thao tác nhập bằng cần điều khiển. Để truy xuất các giá trị hiện tại và trong quá khứ, hãy gọi getAxisValue() hoặc getHistoricalAxisValue(). Bạn cũng có thể tìm thấy số điểm trước đây trong sự kiện cần điều khiển bằng cách gọi getHistorySize().

Đoạn mã sau đây cho thấy cách bạn có thể ghi đè lệnh gọi lại onGenericMotionEvent() để xử lý phương thức nhập bằng cần điều khiển. Trước tiên, bạn nên xử lý các giá trị trong quá khứ 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 thao tác đầ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ó nằm ở giữa hay không, sau đó tính toán chuyển động của trục tương ứng. Cần điều khiển thường có diện tích bằng phẳng, tức là phạm vi các giá trị gần toạ độ (0,0) mà tại đó trục được coi là căn giữa. Nếu giá trị trục do Android báo cáo nằm trong vùng phẳng, thì bạn nên coi bộ điều khiển là không thay đổi (nghĩa là không chuyển động dọc theo cả hai trục).

Đoạn mã dưới đây cho thấy một phương thức trợ giúp tính toán chuyển động dọc theo mỗi trục. Bạn sẽ gọi trình trợ giúp này trong phương thức processJoystickInput() được mô tả kỹ hơn dưới đây.

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

Kết hợp lại với nhau, sau đâ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ợ các tay điều khiển trò chơi có các tính năng tinh vi hơn ngoài một cần điều khiển, hãy làm theo các phương pháp hay nhất sau đây:

  • Xử lý hai thanh đ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 thẻ điều khiển bên trái, Android báo cáo chuyển động theo chiều ngang dưới dạng sự kiện AXIS_X và chuyển động theo chiều dọc là sự kiện AXIS_Y. Đối với cần điều khiển bên phải, Android báo cáo chuyển động theo chiều ngang là sự kiện AXIS_Z và chuyển động theo chiều dọc là sự kiện AXIS_RZ. Hãy nhớ xử lý cả hai thẻ điều khiển trong mã của bạn.
  • Xử lý các thao tác nhấn trình kích hoạt vai (nhưng cung cấp các phương thức nhập thay thế). Một số bộ điều khiển có các điều kiện kích hoạt vai trái và phải. Nếu có các điều kiện kích hoạt này, Android sẽ báo cáo một thao tác nhấn điều kiện kích hoạt bên trái là sự kiện AXIS_LTRIGGER và một thao tác nhấn điều kiện kích hoạt bên phải là sự kiện AXIS_RTRIGGER. Trên Android 4.3 (API cấp 18), tay điều khiển tạo ra AXIS_LTRIGGER cũng báo cáo một giá trị giống nhau 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ượt nhấn điều kiện kích hoạt analog có giá trị được chuẩn hoá từ 0.0 (đã phát hành) đến 1.0 (nhấn hoàn toàn). Không phải tay điều khiển nào cũng có điều kiện kích hoạt, vì vậy, hãy cân nhắc việc cho phép người chơi thực hiện các thao tác đó trong trò chơi bằng các nút khác.