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 rồi 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 thực tế hoặc ghép nối không dây tay điều khiển trò chơi với thiết bị chạy Android, hệ thống sẽ tự động phát hiện tay điều khiển đó là thiết bị đầu vào và bắt đầu báo cáo các sự kiện đầu vào của tay điều khiển. 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 đây trong Activity đang hoạt động hoặc View được lấy tiêu điểm (bạn nên triển khai các lệnh gọi lại cho Activity hoặc View, nhưng không phải 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. Kiểm tra các đối tượng do lệnh gọi lại cung cấp sau đây để biết thông tin về loại sự kiện đầu vào đã nhận được:

KeyEvent
Đối tượng mô tả các sự kiện của nút di chuyển (D-pad) và tay điều khiển trò chơi. Sự kiện phím đ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 qua các lệnh gọi lại sự kiện chính như onKeyDown().
MotionEvent
Đối tượng mô tả dữ liệu đầu vào từ các thao tác di chuyển cần điều khiển và cò vai. 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ã thao tác chỉ định thay đổi trạng thái đã xảy ra, chẳng hạn như cần điều khiển bị di chuyển. Các giá trị trục mô tả vị trí và các thuộc tính di chuyển khác cho một thành phần điều khiển thực 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 tập trung vào cách bạn có thể xử lý dữ liệu đầu vào từ các loại chế độ điều khiển vật lý phổ biến nhất (nút tay điều khiển trò chơi, bàn phím di chuyển và cần điều khiển) trong 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 đã đề cập ở trên và xử lý các đối tượng KeyEventMotionEvent.

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

Khi báo cáo sự kiện đầu vào, Android không phân biệt giữa sự kiện đến từ thiết bị tay điều khiển trò chơi và sự kiện đến 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 một cần điều khiển sẽ 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 từ tay điều khiển trò chơi, thì trước tiên, bạn nên kiểm tra để đảm bảo rằng sự kiện đầu vào đến từ một loại nguồn có liên quan.

Để xác minh 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 gồm 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ó được đặt hay không:

  • Loại nguồn SOURCE_GAMEPAD cho biết thiết bị đầu vào có các nút 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ỉ ra chính xác liệu 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ó các nút điều khiển hướng.
  • Loại nguồn của 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 các 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 một 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 nhập riêng lẻ mà tay điều khiển trò chơi đã kết nối hỗ trợ. Điều này có thể hữu ích, chẳng hạn như nếu bạn muốn trò chơi của mình chỉ sử dụng phương thức nhập từ bộ điều khiển vật lý mà trò chơi hiểu được.

Để phát hiện xem tay điều khiển trò chơi đã kết nối có hỗ trợ một 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 tiên. 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ý 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ạ các mã phím và giá trị trục với các nút điều khiển thực 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.

Chú thích trong hình đề cập đến những nội dung sau:

Các mã phím phổ biến do thao tác nhấn nút trên tay điều khiển trò chơi tạo ra 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 của D-pad. Trò chơi của bạn có thể kiểm tra mã phím 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 mã này đại diện cho một sự kiện có liên quan đến trò chơi, hãy xử lý mã đó dưới dạng một hành động trong trò chơi. Bảng 1 liệt kê các thao tác được đề xuất trong trò chơi cho các nút phổ biến nhất trên tay điều khiển trò chơi.

Bảng 1. Các thao tác được đề xuất trong trò chơi 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 nút
Bắt đầu trò chơi trong trình đơn chính hoặc tạm dừng/bỏ tạm dừng trong khi chơi BUTTON_START*
Hiện trình đơn BUTTON_SELECT*KEYCODE_MENU*
Tương tự 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 được 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 việc cung cấp màn hình cấu hình trong trò chơi để cho phép người dùng cá nhân hoá các mối liên kết 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 đây cho biết cách bạn có thể ghi đè onKeyDown() để liên kết các 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 sẽ coi BUTTON_A là khoá 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à thao tác 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 thành phần đ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 số lần nhấn D-pad LÊN và XUỐNG dưới dạng các sự kiện AXIS_HAT_Y có phạm vi từ -1,0 (lên) đến 1,0 (xuống) và số lần nhấn TRÁI hoặc PHẢI của D-pad là các sự kiện AXIS_HAT_X có phạm vi từ -1,0 (trái) đến 1,0 (phải).

Một số tay điều khiển báo cáo các thao tác 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, bạn nên coi các sự kiện trục mũ và mã phím D-pad là cùng một sự kiện đầu vào, như đề xuất trong bảng 2.

Bảng 2. Các thao tác mặc định được đề xuất trong trò chơi 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.0)
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 (đối với các giá trị từ 0 đến 1.0)

Đoạn mã sau đây hiển thị một lớp trợ giúp cho phép bạn kiểm tra trục mũ và các 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ình trợ giúp này trong trò chơi bất cứ khi nào bạn muốn xử lý đầu vào D-pad (ví dụ: trong 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ã thao tác ACTION_MOVE và vị trí mới của các 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 có xảy ra chuyển động cần quan tâm trên cần điều khiển hay không.

Lưu ý rằng các sự kiện chuyển động của cần điều khiển có thể nhóm nhiều mẫu chuyển động lại với nhau trong một đối tượng duy nhất. Đối tượng MotionEvent chứa vị trí hiện tại của mỗi trục cần điều khiển cũng như nhiều vị trí trước đây của mỗi trục. Khi báo cáo 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ẽ phân lô các giá trị trục để đảm bảo 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 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.

Bạn có thể sử dụng thông tin trong quá khứ để kết xuất chính xác hơn chuyển động của đối tượng trò chơi dựa trên dữ liệu đầu vào của cần điều khiển. Để truy xuất giá trị hiện tại và giá trị trong quá khứ, hãy gọi getAxisValue() hoặc getHistoricalAxisValue(). Bạn cũng có thể tìm thấy số lượng đ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ý 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 phương thức nhập của cần điều khiển, bạn cần xác định xem cần điều khiển có ở giữa hay không, sau đó tính toán chuyển động của trục cho phù hợp. Cần điều khiển thường có một vùng bằ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à chính giữa. Nếu giá trị trục mà 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 tĩnh (tức là không di chuyển 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 gọi trình trợ giúp này trong phương thức processJoystickInput() như mô tả kỹ hơn bên dưới.

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óm lại, sau đây là cách bạn có thể xử lý các 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ợ 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, hãy làm theo các phương pháp hay nhất sau:

  • Xử lý các thanh điều khiển kép. 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 thanh điều khiển bên trái, Android báo cáo các chuyển động theo chiều ngang là sự kiện AXIS_X và chuyển động theo chiều dọc là sự kiện AXIS_Y. Đối với thanh bên phải, Android báo cáo chuyển động 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 cần điều khiển trong mã của bạn.
  • Xử lý các thao tác nhấn 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ó bộ kích hoạt vai trái và vai phải. Khi xuất hiện, các trình kích hoạt này sẽ phát ra một sự kiện AXIS_*TRIGGER hoặc KEYCODE_BUTTON_*2 hoặc cả hai. Đối với điều kiện kích hoạt bên trái, đó là AXIS_LTRIGGERKEYCODE_BUTTON_L2. Đối với nút kích hoạt bên phải, đó sẽ là AXIS_RTRIGGERKEYCODE_BUTTON_R2. Các sự kiện trục chỉ xảy ra nếu trình kích hoạt phát đi một khoảng giá trị trong khoảng từ 0 đến 1 và một số bộ điều khiển có các sự kiện nút phát ra đầu ra analog ngoài các sự kiện trục. Trò chơi phải hỗ trợ cả sự kiện AXIS_KEYCODE_BUTTON_ để tương thích với tất cả tay điều khiển trò chơi phổ biến, nhưng ưu tiên sự kiện phù hợp nhất với lối chơi của bạn nếu 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 giá trị chuẩn hoá từ 0 (đã nhả) đến 1 (đã nhấn hết).
  • Hành vi và khả năng hỗ trợ cụ thể có thể khác nhau trong môi trường được mô phỏng. Các nền tảng mô phỏng (chẳng hạn như Google Play Games) có thể khác nhau đôi chút về hành vi dựa trên khả năng của hệ điều hành của máy chủ lưu trữ. Ví dụ: một số tay điều khiển phát cả sự kiện AXIS_KEYCODE_BUTTON_ chỉ phát các sự kiện AXIS_ và một số bộ điều khiển có thể bị thiếu hoàn toàn tính năng hỗ trợ.