コントローラの操作を処理する

コントローラには次の 2 種類のアクションがあります。

  • オンとオフのバイナリ状態を持つボタンに使用される KeyEvent
  • 値の範囲を返す軸に使用される MotionEvent。アナログスティックの場合は -1 ~ 1、アナログ トリガーの場合は 0 ~ 1 など。

これらの入力は、focus を持つ View から読み取ることができます。

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 をゲームのプレーヤーに関連付けて、接続されている各プレーヤーのゲーム アクションを個別に処理することができます。1 台の 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++ デベロッパーは、ゲーム コントローラ ライブラリを使用する必要があります。すべてのコントローラを最も一般的な機能のサブセットに統合し、ボタン レイアウトを検出する機能など、コントローラ間の一貫したインターフェースを提供します。

この図は、Android ゲーム デベロッパーが Android での一般的なコントローラの外観を想定できることを示しています。

D-pad、アナログ スティック、ボタンなどの入力にラベルが付けられた一般的なゲーム コントローラ
図 1. 一般的なゲーム コントローラのプロファイル

この表に、ゲーム コントローラの標準イベント名とタイプを示します。イベントの完全なリストについては、一般的なバリエーションをご覧ください。システムは、onGenericMotionEventKeyEvent のイベントを onKeyDownonKeyUp を通して送信します。MotionEvent

コントローラの入力 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() を使用してボタンを 1 回だけ受信するようにしてください。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);
      }
    }
    

十字キーの入力を処理する

4 方向の十字キー(D-pad)は、多くのゲーム コントローラで一般的な物理コントロールとして使用されています。Android は、D-pad の UP と DOWN の押下を AXIS_HAT_Y イベントとしてレポートします。-1.0 は上、1.0 は下を示します。D-pad の LEFT または RIGHT の押下を AXIS_HAT_X イベントとしてレポートします。-1.0 は左、1.0 は右を示します。

D-pad の押下をキーコードでレポートするコントローラもあります。ゲームで D-pad の押下を処理する場合、表 2 に示すように、ハット軸のイベントと D-pad のキーコードを同じ入力イベントとして処理する必要があります。

表 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 が ACTION_MOVE アクション コードとジョイスティックの軸の最新の位置を含む MotionEvent をレポートします。ゲームは MotionEvent から提供されたデータを使用して、処理対象のジョイスティックの動きが行われたかどうかを判断します。

ジョイスティックのモーション イベントは、複数の動きのサンプルを 1 つのオブジェクトにまとめることがあるので注意してください。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
}

1 つのジョイスティックでは実現できない高度な機能を備えたゲーム コントローラをサポートするには、以下のベスト プラクティスに従います。

  • デュアル コントローラ スティックに対応する。多くのゲーム コントローラには左右にジョイスティックがあります。左スティックについては、水平方向の動きを AXIS_X イベントとして、垂直方向の動きを AXIS_Y イベントとしてレポートします。右スティックについては、水平方向の動きを AXIS_Z イベントとして、垂直方向の動きを AXIS_RZ イベントとしてレポートします。必ず、両方のコントローラ スティックをコード内で処理してください。
  • ショルダー トリガーの押下に対応する(また、ゲームが AXIS_ イベントと KEYCODE_BUTTON_ イベントに対応していることを確認する)。一部のコントローラには左右にショルダー トリガーがあります。これらのトリガーが存在する場合、AXIS_*TRIGGER イベントまたは KEYCODE_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 Games などのエミュレートされたプラットフォームは、ホスト オペレーティング システムの機能に基づいて動作が若干異なる場合があります。たとえば、AXIS_ イベントと KEYCODE_BUTTON_ イベントの両方を送信する一部のコントローラは AXIS_ イベントのみを送信し、一部のコントローラは完全にサポートされていない可能性があります。

一般的なバリエーション

Android ではコントローラに対するサポートが多岐にわたるため、ゲームがプレーヤーベースでバグなく動作することを検証するためのビルドとテストの方法がわかりにくい場合があります。一見するとさまざまなコントローラがあるように見えますが、世界中のコントローラ メーカーは、3 種類のコントローラ スタイルを常に採用する傾向があります。一部のデバイスでは、これらの 2 つ以上を切り替えるハードウェア トグルが用意されています。

つまり、開発チーム内で 3 つのコントローラでテストするだけで、許可リストや拒否リストを使用しなくてもゲームをプレイできることを確認できます。

一般的なコントローラの種類

コントローラの最も一般的なスタイルは、人気のあるゲーム機のレイアウトを模倣する傾向があります。ボタンラベルとレイアウトの美しさだけでなく、発生するイベントの機能性も考慮されています。ハードウェアでコンソールの種類を切り替えるコントローラは、送信するイベントを変更し、論理ボタンのレイアウトも変更することがよくあります。

テストの際は、各カテゴリのコントローラ 1 つでゲームが動作することを確認することをおすすめします。ファーストパーティ コントローラまたは人気のサードパーティ メーカーでテストすることを選択できます。一般的に、最も人気のあるコントローラは、ベスト エフォートで上記の定義にマッピングされます。

コントローラタイプ 動作の違い ラベル付けのバリエーション
Xbox スタイルのコントローラ

通常、Microsoft Xbox と Windows* プラットフォーム向けに作られたコントローラです。

これらのコントローラは、プロセス コントローラの入力で説明されている機能セットに一致します。 これらのコントローラの L2/R2 ボタンには LT/RT というラベルが付いています。
Switch Style Controllers

これらのコントローラは通常、Nintendo Switch* ファミリーのコンソール向けに設計されています。

これらのコントローラは KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent を送信します。 これらのコントローラの L2/R2 ボタンには ZL/ZR というラベルが付いています。

これらのコントローラでは、A ボタンと B ボタン、X ボタンと Y ボタンも入れ替わるため、KEYCODE_BUTTON_AB とラベル付けされたボタンになり、逆も同様です。

PlayStation スタイルのコントローラ

これらのコントローラは通常、Sony PlayStation* ファミリーのコンソール向けに設計されています。

これらのコントローラは、Xbox スタイルのコントローラのように MotionEvent を送信しますが、完全に押し下げると Switch スタイルのコントローラのように KeyEvent も送信します。 これらのコントローラでは、フェイスボタンに異なるグリフのセットが使用されています。

* 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 の両方を処理できることを確認する必要があります。

サポートされているコントローラ

テストの際は、各カテゴリのコントローラ 1 つでゲームが動作することを確認することをおすすめします。

  • Xbox スタイル
  • Nintendo Switch のスタイル
  • PlayStation スタイル

ファーストパーティ コントローラや一般的なサードパーティ メーカーのコントローラでテストできます。一般的に、最も一般的なコントローラは定義にできるだけ忠実にマッピングされます。