處理控制器動作

控制器有兩種動作:

  • KeyEvent 用於任何具有「開啟」和「關閉」二進位狀態的按鈕
  • MotionEvent 適用於傳回值範圍的任何軸。例如類比搖桿的 -1 到 1,或是類比觸發鍵的 0 到 1。

您可以從具有 focusView 讀取這些輸入內容。

Kotlin

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

  return super.onKeyDown(keyCode, event)
}

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

  return super.onGenericMotionEvent(event)
}

Java

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

  return super.onKeyDown(keyCode, event);
}

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

如有需要,您可以改為直接從 Activity 讀取事件。

確認已連接遊戲控制器

回報輸入事件時,Android 會針對不同類型的輸入裝置重複使用相同的按鍵或軸 ID。舉例來說,觸控螢幕動作會產生代表觸控表面 X 座標的 AXIS_X 事件,但遊戲手把會產生代表左搖桿 X 位置的 AXIS_X 事件。也就是說,您必須檢查來源類型,才能正確解讀輸入事件。

如要確認已連線的 InputDevice 是否為遊戲控制器,請使用 supportsSource(int) 函式:

  • SOURCE_GAMEPAD 來源類型表示輸入裝置有控制器按鈕 (例如 KEYCODE_BUTTON_A)。請注意,這個來源類型不會嚴格指出遊戲控制器是否有 D-pad 按鈕,但大多數控制器通常都有方向控制項。
  • 來源類型 SOURCE_DPAD 表示輸入裝置有 D-Pad 按鈕 (例如 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 (supportsSource(SOURCE_GAMEPAD)
              || supportsSource(SOURCE_JOYSTICK)) {
              // This device is a game controller. Store its device ID.
              gameControllerDeviceIds
                  .takeIf { !it.contains(deviceId) }
                  ?.add(deviceId)
          }
      }
  }
  return gameControllerDeviceIds
}

Java

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

      if (dev == null) {
          continue;
      }

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

處理控制器輸入內容

本節說明 Android 支援的遊戲控制器類型。

C++ 開發人員應使用遊戲控制器程式庫。這個 API 會將所有控制器整合至最常見的特徵子集,並在控制器之間提供一致的介面,包括偵測按鈕配置的功能。

這張圖顯示 Android 遊戲開發人員在 Android 上常見的控制器外觀。

一般遊戲控制器,標示的輸入包括 D-Pad、類比搖桿和按鈕
圖 1. 一般遊戲控制器的設定檔。

下表列出遊戲控制器的標準事件名稱和類型。如需完整事件清單,請參閱「常見變數」。系統會透過 onGenericMotionEventKeyEvent 事件傳送 MotionEvent 事件,並透過 onKeyDownonKeyUp 傳送 KeyEvent 事件。

控制器輸入 KeyEvent MotionEvent
1. D-Pad
AXIS_HAT_X
(水平輸入)
AXIS_HAT_Y
(垂直輸入)
2. 左類比搖桿
KEYCODE_BUTTON_THUMBL
(按下時)
AXIS_X
(水平移動)
AXIS_Y
(垂直移動)
3. 右類比搖桿
KEYCODE_BUTTON_THUMBR
(按下時)
AXIS_Z
(水平移動)
AXIS_RZ
(垂直移動)
4. X 按鈕 KEYCODE_BUTTON_X
5. A 按鈕 KEYCODE_BUTTON_A
6. Y 按鈕 KEYCODE_BUTTON_Y
7. B 按鈕 KEYCODE_BUTTON_B
8. 右側緩衝區
KEYCODE_BUTTON_R1
9. 右側觸發鍵
AXIS_RTRIGGER
10. 左側觸發鍵 AXIS_LTRIGGER
11. 左側緩衝區 KEYCODE_BUTTON_L1
12. 開始 KEYCODE_BUTTON_START
13. 選取 KEYCODE_BUTTON_SELECT

處理按鈕的按下動作

由於 Android 回報控制器按鈕按壓的方式與鍵盤按鈕按壓相同,因此您需要:

  • 確認事件來自 SOURCE_GAMEPAD
  • 請務必只透過 KeyEvent.getRepeatCount() 接收按鈕一次,否則 Android 會傳送重複的按鍵事件,就像按住鍵盤按鍵一樣。
  • 傳回 true,表示事件已處理完畢。
  • 將未處理的事件傳遞至 super,確認 Android 的各種相容性層級運作正常。

    Kotlin

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

    Java

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

處理方向鍵輸入內容

四向方向鍵 (或 D-Pad) 是許多遊戲控制器常見的實體控制項。Android 會將 D-pad 的上和下按鈕按下動作回報為 AXIS_HAT_Y 事件,其中 -1.0 表示向上,1.0 表示向下。系統會將按下 D-Pad 左鍵或右鍵回報為 AXIS_HAT_X 事件,其中 -1.0 表示左鍵,1.0 表示右鍵。

部分控制器會改用鍵碼回報方向鍵按鈕按下事件。如果遊戲會留意 D-Pad 按鈕按下事件,您應將帽軸事件和 D-Pad 鍵碼視為相同的輸入事件,如表 2 所示。

表 2. 建議的 D-pad 鍵碼和帽軸值預設遊戲動作。

遊戲動作 D-pad 鍵碼 帽軸代碼
往上移動 KEYCODE_DPAD_UP AXIS_HAT_Y (適用於 0 至 -1.0 的值)
往下移動 KEYCODE_DPAD_DOWN AXIS_HAT_Y (值介於 0 到 1.0)
向左移 KEYCODE_DPAD_LEFT AXIS_HAT_X (適用於 0 至 -1.0 的值)
向右移 KEYCODE_DPAD_RIGHT AXIS_HAT_X (值介於 0 到 1.0)

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

Kotlin

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            return event.isFromSource(InputDevice.SOURCE_DPAD)
    }
}

Java

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        return event.isFromSource(InputDevice.SOURCE_DPAD);
     }
}

您可以在遊戲中任何想處理 D-Pad 輸入的位置使用這個輔助類別 (例如 onGenericMotionEvent()onKeyDown() 回呼)。

例如:

Kotlin

private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if (Dpad.isDpadDevice(event)) {
        when (dpad.getDirectionPressed(event)) {
            Dpad.LEFT -> {
                // Do something for LEFT direction press
                ...
                return true
            }
            Dpad.RIGHT -> {
                // Do something for RIGHT direction press
                ...
                return true
            }
            Dpad.UP -> {
                // Do something for UP direction press
                ...
                return true
            }
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

Java

Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = dpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

處理搖桿移動

當玩家移動遊戲控制器上的搖桿時,Android 會回報 MotionEvent,其中包含 ACTION_MOVE 動作代碼和搖桿軸的更新位置。遊戲可以使用 MotionEvent 提供的資料,判斷是否發生遊戲關心的搖桿移動。

請注意,搖桿動作事件可能會將多個動作樣本批次處理為單一物件。MotionEvent 物件包含每個搖桿軸的目前位置,以及每個軸的多個歷史位置。回報動作事件時,如果使用動作程式碼 ACTION_MOVE (例如搖桿移動),Android 會批次處理軸值,以提高效率。軸的歷史值包含一組與目前軸值不同,但比先前任何動作事件回報值更新的值。詳情請參閱 MotionEvent 參考資料。

如要根據搖桿輸入內容準確算繪遊戲物件的移動,可以使用 MotionEvent 物件提供的歷來資訊。

您可以使用下列方法擷取目前和歷史值:

下列程式碼片段顯示如何覆寫 onGenericMotionEvent() 回呼,以處理搖桿輸入。您應先處理軸的歷史值,再處理目前位置。

Kotlin

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

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {

        // Check that the event came from a game controller
        return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                && event.action == MotionEvent.ACTION_MOVE) {

            // Process the movements starting from the
            // earliest historical position in the batch
            (0 until event.historySize).forEach { i ->
                // Process the event at historical position i
                processJoystickInput(event, i)
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1)
            true
        } else {
            super.onGenericMotionEvent(event)
        }
    }
}

Java

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

使用搖桿輸入前,請先判斷搖桿是否位於中心位置, 然後據此計算軸向移動。搖桿通常會有平坦區域,也就是軸心視為中心時,座標接近 (0,0) 的值範圍。如果 Android 回報的軸值落在平坦區域內,您應將控制器視為靜止狀態 (也就是沿著兩個軸都靜止不動)。

這個程式碼片段顯示的輔助方法會計算每個軸的移動量。您會在 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_ 事件,且可能完全不支援部分控制器。

常見變體

Android 支援的控制器種類繁多,因此您可能不清楚如何建構及測試,才能確保遊戲在玩家群中運作無誤。我們發現,儘管控制器種類繁多,但全球各地的控制器製造商往往會遵循三種不同的控制器樣式。部分裝置提供硬體切換鍵,可在其中兩項或多項之間切換。

也就是說,您可以在開發團隊中測試至少三個控制器,並確信遊戲可玩,不必使用允許和拒絕清單。

常見的控制器類型

最常見的控制器樣式通常會模仿熱門遊戲機的版面配置。這不僅是按鈕標籤和版面配置的美學,也是由引發的事件所決定的功能。如果控制器有硬體切換鈕,可在不同主機類型之間切換,則控制器傳送的事件會有所不同,甚至連邏輯按鈕配置也經常會改變。

測試時,建議您驗證遊戲是否能搭配各類別中的一個控制器運作。您可以選擇使用第一方控制器, 或熱門的第三方製造商控制器進行測試。一般來說,我們會盡量將最熱門的控制器對應至上述定義

控制器類型 行為差異 標籤變化版本
Xbox 風格控制器

這類控制器通常是為 Microsoft Xbox 和 Windows* 平台打造。

這些控制器符合「處理控制器輸入內容」一節所述的功能集。 這些控制器上的 L2/R2 按鈕標示為 LT/RT
切換樣式控制器

這類控制器通常是為 Nintendo Switch* 系列主機設計。

這些控制器會傳送 KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent 這些控制器上的 L2/R2 按鈕標示為 ZL/ZR。

這些控制器也會交換 AB 按鈕,以及 XY 按鈕,因此 KEYCODE_BUTTON_A 是標示為 B 的按鈕,反之亦然。

PlayStation 樣式控制器

這類控制器通常是為 Sony PlayStation* 系列主機設計。

這些控制器會傳送 MotionEvent,例如 Xbox 樣式的控制器,但完全按下時也會傳送 KeyEvent,例如 Switch 樣式的控制器 這些控制器使用的臉部按鈕字形不同。

* Microsoft、Xbox 和 Windows 是 Microsoft 的註冊商標; Nintendo Switch 是 Nintendo of America Inc. 的註冊商標; PlayStation 是 Sony Interactive Entertainment Inc. 的註冊商標。

釐清觸發按鈕

部分控制器會傳送 AXIS_LTRIGGERAXIS_RTRIGGER,部分會傳送 KEYCODE_BUTTON_L2KEYCODE_BUTTON_R2,其他則會根據硬體功能傳送所有這些事件。支援所有這些事件,盡可能提高相容性。

所有傳送 AXIS_LTRIGGER 的控制器也會傳送 AXIS_BRAKE,同樣地,AXIS_RTRIGGERAXIS_GAS 也有助於盡量提高賽車方向盤和一般遊戲控制器的相容性。一般來說,這不會造成問題,但請注意按鍵重新對應畫面等功能。

觸發條件 MotionEvent KeyEvent
左側觸發鍵 AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
右側觸發鍵 AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

請務必確認遊戲可以處理 KeyEventMotionEvent,盡可能維持與更多控制器的相容性,並確保事件已去重複。

支援的控制器

測試時,建議您驗證遊戲是否能搭配各類別中的一個控制器運作。

  • Xbox 風格
  • Nintendo Switch 風格
  • PlayStation 風格

您可以測試第一方控制器或熱門第三方製造商的控制器,我們通常會盡可能將最熱門的控制器對應至定義。