處理控制器動作

在系統層級,Android 會將遊戲控制器的輸入事件代碼回報為 Android 鍵碼和軸值。在遊戲中,您可以接收這些代碼和值,並將其轉換為特定遊戲內動作。

當玩家實際連結遊戲控制器或以無線方式將遊戲控制器與 Android 裝置配對時,系統就會自動將控制器偵測為輸入裝置,並開始回報輸入事件。遊戲可以在有效的 Activity 或已聚焦的 View 中實作下列回呼方法,以便接收這些輸入事件 (您應為 ActivityView 實作回呼,但不能同時為兩者實作):

建議的方法是從使用者互動的特定 View 物件擷取事件。檢查回呼提供的下列物件,以取得所收到輸入事件類型的相關資訊:

KeyEvent
用於描述方向鍵 (D-Pad) 和遊戲控制器按鈕事件的物件。按鍵事件會與「按鍵程式碼」一併提供,以指出觸發的特定按鈕,例如 DPAD_DOWNBUTTON_A。您可以呼叫 getKeyCode() 或從主要事件回呼 (例如 onKeyDown()) 取得金鑰程式碼。
MotionEvent
這個物件可說明搖桿和肩部扳機動作的輸入內容。動作事件會隨附動作代碼和一組軸值。動作碼會指定發生的狀態變更,例如搖桿移動。軸值會說明特定實體控制項 (例如 AXIS_XAXIS_RTRIGGER) 的位置和其他移動屬性。您可以呼叫 getAction() 取得動作代碼,並呼叫 getAxisValue() 取得軸值。

本課程將著重於如何在遊戲畫面中,透過實作上述的 View 回呼方法及處理 KeyEventMotionEvent 物件,處理最常見類型的實體控制項 (遊戲控制器按鈕、方向鍵和搖桿) 輸入內容。

確認遊戲控制器已連線

回報輸入事件時,Android 不會區分來自非遊戲控制器裝置的事件,以及來自遊戲控制器的事件。舉例來說,觸控螢幕動作會產生 AXIS_X 事件,代表觸控表面的 X 座標,但搖桿會產生 AXIS_X 事件,代表搖桿的 X 位置。如果遊戲重視處理遊戲控制器的輸入作業,請先檢查輸入事件是否來自相關的來源類型。

如要確認已連結的輸入裝置是否為遊戲控制器,請呼叫 getSources() 來取得該裝置支援的輸入來源類型合併位元欄位。接著,您可以測試是否已設定下列欄位:

  • SOURCE_GAMEPAD 的來源類型表示輸入裝置具有遊戲手把按鈕 (例如 BUTTON_A)。請注意,雖然大多數遊戲控制器通常都具有方向性控制項,但此來源類型並未嚴格指出遊戲控制器是否具有 D-Pad 按鈕。
  • 來源類型為 SOURCE_DPAD 表示輸入裝置有方向鍵按鈕 (例如 DPAD_UP)。
  • SOURCE_JOYSTICK 來源類型表示輸入裝置具有類比控制桿 (例如,記錄 AXIS_XAXIS_Y 沿途動作的搖桿)。

下列程式碼片段是輔助方法,可讓您檢查已連結的輸入裝置是否為遊戲控制器。如果是,方法會擷取遊戲控制器的裝置 ID。接著,您可以將每個裝置 ID 與遊戲中的玩家建立關聯,並分別處理每位已連線玩家的遊戲動作。如要進一步瞭解如何支援在同一部 Android 裝置上同時連接多個遊戲控制器,請參閱「支援多個遊戲控制器」。

Kotlin

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

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

Java

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

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

此外,您可能還想檢查已連結的遊戲控制器是否支援個別輸入功能。舉例來說,如果您希望遊戲只使用其瞭解的實體控制項輸入內容,這項功能就非常實用。

如要偵測連接的遊戲控制器是否支援特定按鍵代碼或軸代碼,請使用下列技巧:

  • 在 Android 4.4 (API 級別 19) 以上版本中,您可以呼叫 hasKeys(int...),判斷已連結的遊戲控制器是否支援某個按鍵代碼。
  • 在 Android 3.1 (API 級別 12) 以上版本中,您可以先呼叫 getMotionRanges(),找出已連結遊戲控制器所支援的所有可用軸。接著,在每個傳回的 InputDevice.MotionRange 物件上呼叫 getAxis() 來取得其軸 ID。

處理遊戲手把按鈕的按下動作

圖 1 顯示 Android 如何將按鍵代碼和軸值對應至大多數遊戲控制器的實體控制項。

圖 1. 通用遊戲控制器的設定檔。

圖中的圖說代表以下項目:

按下遊戲控制器按鈕時產生的常見按鍵代碼包括 BUTTON_ABUTTON_BBUTTON_SELECTBUTTON_START。當使用者按下 D-Pad 跨列的中心點時,部分遊戲控制器也會觸發 DPAD_CENTER 按鍵程式碼。遊戲可以透過呼叫 getKeyCode() 或從按鍵事件回呼 (例如 onKeyDown()) 檢查按鍵程式碼,如果該程式碼代表與遊戲相關的事件,則可將其視為遊戲動作加以處理。表 1 列出最常見遊戲搖桿按鈕的建議遊戲動作。

表 1. 遊戲手把按鈕的建議遊戲動作。

遊戲動作 按鈕鍵碼
在主選單中啟動遊戲,或在遊戲期間暫停/恢復暫停 BUTTON_START*
顯示選單 BUTTON_SELECT*KEYCODE_MENU*
與「導覽」設計指南中所述的 Android 返回導覽行為相同。 KEYCODE_BACK
返回選單中的上一個項目 BUTTON_B
確認選取項目,或執行主要遊戲動作 BUTTON_ADPAD_CENTER

* 遊戲不應依賴「開始」、「選取」或「選單」按鈕的存在。

提示: 建議您在遊戲中提供設定畫面,讓使用者為遊戲動作自訂遊戲控制器對應項目。

下列程式碼片段說明如何覆寫 onKeyDown(),將 BUTTON_ADPAD_CENTER 按鈕按下動作與遊戲動作建立關聯。

Kotlin

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

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

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

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

Java

public class GameView extends View {
    ...

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

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

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

注意:在 Android 4.2 (API 級別 17) 以下版本中,系統會將 BUTTON_A 視為 Android Back 鍵。如果您的應用程式支援這些 Android 版本,請務必將 BUTTON_A 視為主要遊戲動作。如要判斷裝置目前的 Android SDK 版本,請參閱 Build.VERSION.SDK_INT 值。

處理方向鍵輸入

4 向方向鍵 (D-Pad) 是許多遊戲控制器中常見的實體控制項。Android 會將 D-pad 向上和向下按鈕的按壓動作,記錄為範圍為 -1.0 (向上) 到 1.0 (向下) 的 AXIS_HAT_Y 事件,並將 D-pad 向左或向右按鈕的按壓動作,記錄為範圍為 -1.0 (向左) 到 1.0 (向右) 的 AXIS_HAT_X 事件。

部分控制器會透過按鍵碼回報 D-Pad 的按下動作。如果您的遊戲會處理方向鍵按下事件,則應將帽狀軸事件和方向鍵按鍵代碼視為相同的輸入事件,如表 2 所示。

表 2. 針對方向鍵按鍵代碼和帽軸值,提供建議的預設遊戲動作。

遊戲動作 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 的值)

下列程式碼片段顯示輔助類別,可讓您檢查輸入事件中的帽狀軸和鍵碼值,以便判斷方向鍵方向。

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

您可以在遊戲中,針對要處理 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 參考資料。

您可以使用歷來資訊,根據搖桿輸入內容,更準確地算繪遊戲物件的移動情形。如要擷取目前和歷來值,請呼叫 getAxisValue()getHistoricalAxisValue()。您也可以呼叫 getHistorySize(),找出搖桿事件中的歷史點數數量。

下列程式碼片段說明如何覆寫 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 回報的軸值落在平坦區域內,您應將控制器視為處於靜止狀態 (也就是沿著兩個軸不動)。

以下程式碼片段顯示輔助方法,可計算沿著每個軸線的移動情形。您可以在 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 事件的形式回報。請務必在程式碼中處理兩個控制器搖桿。
  • 處理肩觸發的按下動作,並確保遊戲支援 AXIS_KEYCODE_BUTTON_ 事件。部分控制器有左右肩部觸發器。當這些觸發事件出現時,會發出 AXIS_*TRIGGERKEYCODE_BUTTON_*2 事件,或同時發出這兩種事件。左邊的扳機鍵則是 AXIS_LTRIGGERKEYCODE_BUTTON_L2。對於右側的觸發器,則是 AXIS_RTRIGGERKEYCODE_BUTTON_R2。只有在觸發器發出 0 到 1 之間的值範圍時,才會發生軸向事件,而某些具有類比輸出的控制器除了軸向事件外,還會發出按鈕事件。遊戲必須同時支援 AXIS_KEYCODE_BUTTON_ 事件,才能與所有常見的遊戲控制器相容。如果控制器同時回報這兩種事件,請選擇最適合遊戲玩法的事件。在 Android 4.3 (API 級別 18) 以上版本中,產生 AXIS_LTRIGGER 的控制器也會回報 AXIS_BRAKE 軸的相同值。AXIS_RTRIGGERAXIS_GAS 也是如此。Android 會以 0.0 (已釋放) 到 1.0 (已完全按下) 的正規化值,回報所有類比觸發器按下事件。
  • 在模擬環境中,特定行為和支援功能可能會有所不同。模擬平台 (例如 Google Play 遊戲) 的行為可能會因主機作業系統的功能而略有不同。舉例來說,某些同時發出 AXIS_KEYCODE_BUTTON_ 事件的控制器,可能只會發出 AXIS_ 事件,而某些控制器可能完全不支援。