Menangani tindakan pengontrol

Pengontrol memiliki dua jenis tindakan:

  • KeyEvent digunakan untuk tombol apa pun dengan status biner "aktif" dan "nonaktif"
  • MotionEvent digunakan untuk sumbu apa pun yang menampilkan rentang nilai. Misalnya -1 hingga 1 untuk stik analog atau 0 hingga 1 untuk pemicu analog.

Anda dapat membaca input ini dari View yang memiliki focus.

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

Jika perlu, Anda dapat membaca peristiwa dari Activity secara langsung.

Memverifikasi bahwa pengontrol game telah tersambung

Saat melaporkan peristiwa input, Android akan menggunakan kembali ID sumbu atau tombol yang sama untuk berbagai jenis perangkat input. Misalnya, tindakan layar sentuh menghasilkan peristiwa AXIS_X yang mewakili koordinat X permukaan sentuh, tetapi gamepad menghasilkan peristiwa AXIS_X yang mewakili posisi X stik kiri. Artinya, Anda harus memeriksa jenis sumber untuk menafsirkan peristiwa input dengan benar.

Untuk memverifikasi bahwa InputDevice yang terhubung adalah pengontrol game, gunakan fungsi supportsSource(int):

  • Jenis sumber SOURCE_GAMEPAD menunjukkan bahwa perangkat input memiliki tombol pengontrol (misalnya, KEYCODE_BUTTON_A). Perhatikan bahwa jenis sumber ini tidak hanya menunjukkan jika pengontrol game memiliki tombol D-pad, meskipun sebagian besar pengontrol biasanya memiliki kontrol arah.
  • Jenis sumber SOURCE_DPAD menunjukkan bahwa perangkat input memiliki tombol D-pad (misalnya, DPAD_UP).
  • Jenis sumber SOURCE_JOYSTICK menunjukkan bahwa perangkat input memiliki stick kontrol analog (misalnya, joystick yang mencatat pergerakan sepanjang AXIS_X dan AXIS_Y).

Cuplikan kode berikut menunjukkan metode helper yang memungkinkan Anda memeriksa apakah perangkat input yang tersambung merupakan pengontrol game. Jika ya, metode tersebut mengambil ID perangkat untuk pengontrol game. Kemudian, Anda dapat mengaitkan setiap ID perangkat dengan pemain di game Anda, dan memproses tindakan game untuk setiap pemain yang terhubung secara terpisah. Untuk mempelajari lebih lanjut cara mendukung beberapa pengontrol game yang terhubung secara bersamaan ke perangkat berteknologi Android yang sama, lihat Mendukung beberapa pengontrol game.

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

Memproses input pengontrol

Bagian ini menjelaskan jenis pengontrol game yang didukung di Android.

Developer C++ harus menggunakan Game Controller Library. API ini menyatukan semua pengontrol ke subset fitur yang paling umum dan menyediakan antarmuka yang konsisten di antara pengontrol tersebut, termasuk kemampuan untuk mendeteksi tata letak tombol.

Gambar ini menunjukkan tampilan pengontrol umum di Android yang dapat diharapkan oleh developer game Android.

Pengontrol game generik dengan input berlabel, termasuk D-Pad, stick analog, dan tombol
Gambar 1. Profil untuk pengontrol game umum.

Tabel ini mencantumkan nama dan jenis peristiwa standar untuk pengontrol game. Untuk daftar lengkap peristiwa, lihat Varian umum. Sistem mengirim peristiwa MotionEvent melalui peristiwa onGenericMotionEvent dan KeyEvent melalui onKeyDown dan onKeyUp.

Input Pengontrol KeyEvent MotionEvent
1. D-Pad
AXIS_HAT_X
(input horizontal)
AXIS_HAT_Y
(input vertikal)
2. Stik Analog Kiri
KEYCODE_BUTTON_THUMBL
(saat ditekan)
AXIS_X
(gerakan horizontal)
AXIS_Y
(gerakan vertikal)
3. Stik Analog Kanan
KEYCODE_BUTTON_THUMBR
(saat ditekan)
AXIS_Z
(gerakan horizontal)
AXIS_RZ
(gerakan vertikal)
4. Tombol X KEYCODE_BUTTON_X
5. Tombol A KEYCODE_BUTTON_A
6. Tombol Y KEYCODE_BUTTON_Y
7. Tombol B KEYCODE_BUTTON_B
8. Bumper Kanan
KEYCODE_BUTTON_R1
9. Tombol Kanan
AXIS_RTRIGGER
10. Tombol Pemicu Kiri AXIS_LTRIGGER
11. Tombol Bumper Kiri KEYCODE_BUTTON_L1
12. Mulai KEYCODE_BUTTON_START
13. Pilih KEYCODE_BUTTON_SELECT

Menangani penekanan tombol

Karena Android melaporkan penekanan tombol pengontrol secara identik dengan penekanan tombol keyboard, Anda perlu:

  • Validasi bahwa acara berasal dari SOURCE_GAMEPAD.
  • Pastikan Anda hanya menerima tombol satu kali dengan KeyEvent.getRepeatCount(), Android akan mengirim peristiwa tombol berulang seperti jika Anda menahan tombol keyboard.
  • Menunjukkan bahwa peristiwa ditangani dengan menampilkan true.
  • Teruskan peristiwa yang tidak tertangani ke super untuk memverifikasi bahwa berbagai lapisan kompatibilitas Android berfungsi dengan tepat.

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

Memproses input tombol arah

Tombol 4 arah, atau D-pad, merupakan kontrol fisik yang umum di banyak pengontrol game. Android melaporkan penekanan D-pad ATAS dan BAWAH sebagai peristiwa AXIS_HAT_Y, dengan -1,0 menunjukkan atas dan 1,0 menunjukkan bawah. Peristiwa ini melaporkan penekanan D-pad KIRI atau KANAN sebagai peristiwa AXIS_HAT_X, dengan -1,0 menunjukkan kiri dan 1,0 menunjukkan kanan.

Namun, beberapa pengontrol melaporkan penekanan D-pad dengan kode tombol. Jika game Anda peduli dengan penekanan D-pad, Anda harus memperlakukan peristiwa sumbu hat dan kode tombol D-pad sebagai peristiwa input yang sama, seperti yang direkomendasikan di tabel 2.

Tabel 2. Tindakan game default yang direkomendasikan untuk kode tombol D-pad dan nilai sumbu hat.

Tindakan Game Kode Tombol D-pad Kode Sumbu Hat
Naikkan KEYCODE_DPAD_UP AXIS_HAT_Y (untuk nilai 0 hingga -1,0)
Turunkan KEYCODE_DPAD_DOWN AXIS_HAT_Y (untuk nilai 0 hingga 1,0)
Pindahkan ke Kiri KEYCODE_DPAD_LEFT AXIS_HAT_X (untuk nilai 0 hingga -1,0)
Pindahkan ke Kanan KEYCODE_DPAD_RIGHT AXIS_HAT_X (untuk nilai 0 hingga 1,0)

Cuplikan kode berikut menunjukkan class helper yang memungkinkan Anda memeriksa nilai kode tombol dan sumbu hat dari peristiwa input untuk menentukan arah 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);
     }
}

Anda dapat menggunakan class helper ini di game Anda kapan pun Anda ingin memproses input D-pad (misalnya, di callback onGenericMotionEvent() atau onKeyDown()).

Contoh:

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

Memproses pergerakan joystick

Saat pemain menggerakkan joystick di pengontrol game mereka, Android melaporkan MotionEvent yang berisi kode tindakan ACTION_MOVE dan posisi sumbu joystick yang diperbarui. Game Anda dapat menggunakan data yang disediakan oleh MotionEvent untuk menentukan apakah pergerakan joystick yang dipedulikannya terjadi.

Perlu diperhatikan bahwa peristiwa gerakan joystick mungkin menumpuk beberapa contoh pergerakan bersama dengan satu objek. Objek MotionEvent berisi posisi saat ini untuk setiap sumbu joystick serta beberapa posisi historis untuk setiap sumbu. Saat melaporkan peristiwa gerakan dengan kode tindakan ACTION_MOVE (seperti pergerakan joystick), Android akan menumpuk nilai sumbu demi efisiensi. Nilai historis untuk sumbu terdiri dari serangkaian nilai berbeda yang lebih lama dari nilai sumbu saat ini, dan lebih baru dari nilai yang dilaporkan di semua peristiwa gerakan sebelumnya. Lihat referensi MotionEvent untuk mengetahui detailnya.

Untuk merender pergerakan objek game berdasarkan input joystick secara akurat, Anda dapat menggunakan informasi historis yang disediakan oleh objek MotionEvent.

Anda dapat mengambil nilai saat ini dan historis menggunakan metode berikut:

Cuplikan berikut menunjukkan cara menggantikan callback onGenericMotionEvent() untuk memproses input joystick. Pertama Anda harus memproses nilai historis untuk sumbu, kemudian memproses posisinya saat ini.

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

Sebelum menggunakan input joystick, Anda perlu menentukan apakah joystick terpusat, kemudian menghitung pergerakan sumbunya berdasarkan informasi tersebut. Joystick biasanya memiliki area flat, yaitu, rentang nilai yang dekat dengan koordinat (0,0) yang pada posisi tersebut sumbu dianggap terpusat. Jika nilai sumbu yang dilaporkan oleh Android berada dalam area datar, Anda harus memperlakukan pengontrol agar diistirahatkan (yaitu, tidak bergerak di sepanjang kedua sumbu).

Cuplikan menunjukkan metode helper yang menghitung pergerakan di sepanjang setiap sumbu. Anda memanggil helper ini di metode processJoystickInput() yang dijelaskan lebih lanjut dalam contoh berikut:

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

Dengan menggabungkan semua informasi di atas, berikut cara Anda dapat memproses pergerakan joystick dalam game:

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
}

Untuk mendukung pengontrol game dengan fitur yang lebih canggih dari sekadar satu joystick, ikuti praktik terbaik berikut:

  • Menangani stick pengontrol ganda. Sebagian besar pengontrol game memiliki joystick kiri dan kanan. Untuk stick kiri, Android melaporkan pergerakan horizontal sebagai peristiwa AXIS_X dan pergerakan vertikal sebagai peristiwa AXIS_Y. Untuk stick sebelah kanan, Android melaporkan pergerakan horizontal sebagai peristiwa AXIS_Z dan pergerakan vertikal sebagai peristiwa AXIS_RZ. Pastikan untuk menangani kedua stick pengontrol di kode Anda.
  • Menangani penekanan shoulder trigger (dan memastikan game Anda berfungsi dengan peristiwa AXIS_ dan KEYCODE_BUTTON_). Beberapa pengontrol memiliki shoulder trigger kiri dan kanan. Jika ada, pemicu ini akan memancarkan peristiwa AXIS_*TRIGGER atau KEYCODE_BUTTON_*2, atau keduanya. Untuk pemicu kiri, nilainya adalah AXIS_LTRIGGER dan KEYCODE_BUTTON_L2. Untuk pemicu kanan, nilainya adalah AXIS_RTRIGGER dan KEYCODE_BUTTON_R2. Peristiwa sumbu hanya terjadi jika pemicu memancarkan rentang nilai antara 0 dan 1, dan beberapa pengontrol dengan output analog memancarkan peristiwa tombol selain peristiwa sumbu. Game harus mendukung peristiwa AXIS_ dan KEYCODE_BUTTON_ agar tetap kompatibel dengan semua pengontrol game umum, tetapi lebih memilih peristiwa yang paling masuk akal untuk gameplay Anda jika pengontrol melaporkan keduanya. Di Android 4.3 (level API 18) dan yang lebih tinggi, pengontrol yang menghasilkan AXIS_LTRIGGER juga melaporkan nilai yang identik untuk sumbu AXIS_BRAKE. Hal yang sama berlaku untuk AXIS_RTRIGGER dan AXIS_GAS. Android melaporkan semua penekanan trigger analog dengan nilai yang dinormalkan dari 0,0 (dilepaskan) hingga 1,0 (ditekan sepenuhnya).
  • Perilaku dan dukungan tertentu dapat berbeda di lingkungan yang diemulasi. Platform yang diemulasi, seperti Google Play Game, mungkin sedikit berbeda perilakunya berdasarkan kemampuan sistem operasi host. Misalnya, beberapa pengontrol yang memancarkan peristiwa AXIS_ dan KEYCODE_BUTTON_ hanya memancarkan peristiwa AXIS_, dan dukungan untuk beberapa pengontrol mungkin tidak ada sama sekali.

Variasi umum

Dengan berbagai dukungan yang dimiliki Android untuk pengontrol, mungkin tidak jelas cara membuat dan menguji untuk memverifikasi bahwa game Anda berfungsi bebas bug di antara basis pemain Anda. Kami mendapati bahwa meskipun ada berbagai macam jenis, produsen pengontrol di seluruh dunia cenderung secara konsisten mematuhi tiga gaya pengontrol yang berbeda. Beberapa perangkat menyediakan tombol hardware untuk beralih di antara dua atau lebih mode tersebut.

Artinya, Anda dapat melakukan pengujian dengan minimal tiga pengontrol di antara tim pengembang dan tetap yakin bahwa game Anda dapat dimainkan tanpa menggunakan daftar izinkan dan tolak.

Jenis Pengontrol Umum

Gaya pengontrol yang paling umum cenderung meniru tata letak konsol game populer. Hal ini bersifat estetis dalam tata letak dan label tombol serta fungsional berdasarkan peristiwa yang dimunculkan. Pengontrol dengan tombol hardware di antara berbagai jenis konsol akan mengubah peristiwa yang dikirim dan sering kali bahkan tata letak tombol logisnya.

Saat menguji, sebaiknya Anda memvalidasi bahwa game Anda berfungsi dengan satu pengontrol di setiap kategori. Anda dapat memilih untuk menguji dengan pengontrol pihak pertama, atau produsen pihak ketiga yang populer. Secara umum, kami akan memetakan pengontrol paling populer ke definisi di atas dengan upaya terbaik.

Jenis Pengontrol Perbedaan Perilaku Variasi Pelabelan
Controller Gaya Xbox

Pengontrol ini biasanya dibuat untuk platform Microsoft Xbox dan Windows*.

Pengontrol ini cocok dengan set fitur yang diuraikan dalam Memproses input pengontrol Tombol L2/R2 pada pengontrol ini diberi label LT/RT
Pengontrol Gaya Tombol

Pengontrol ini biasanya dirancang untuk konsol keluarga Nintendo Switch*.

Pengontrol ini mengirimkan KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent Tombol L2/R2 pada pengontrol ini diberi label ZL/ZR.

Pengontrol ini juga menukar tombol A dan B serta tombol X dan Y, sehingga KEYCODE_BUTTON_A adalah tombol berlabel B dan sebaliknya.

Pengontrol Gaya PlayStation

Pengontrol ini biasanya dirancang untuk konsol keluarga Sony PlayStation*.

Pengontrol ini mengirim MotionEvent seperti Pengontrol Gaya Xbox, tetapi juga mengirim KeyEvent seperti Pengontrol Gaya Switch saat ditekan sepenuhnya. Pengontrol ini menggunakan kumpulan glif yang berbeda untuk tombol muka.

* Microsoft, Xbox, dan Windows adalah merek dagang terdaftar dari Microsoft; Nintendo Switch adalah merek dagang terdaftar dari Nintendo of America Inc.; PlayStation adalah merek dagang terdaftar Sony Interactive Entertainment Inc.

Tombol pemicu yang tidak ambigu

Beberapa pengontrol mengirim AXIS_LTRIGGER dan AXIS_RTRIGGER, beberapa mengirim KEYCODE_BUTTON_L2 dan KEYCODE_BUTTON_R2, dan yang lainnya mengirim semua peristiwa ini berdasarkan kemampuan hardware-nya. Maksimalkan kompatibilitas dengan mendukung semua peristiwa ini.

Semua pengontrol yang mengirim AXIS_LTRIGGER juga akan mengirim AXIS_BRAKE, begitu juga dengan AXIS_RTRIGGER dan AXIS_GAS untuk membantu memaksimalkan kompatibilitas antara setir balap dan pengontrol game biasa. Umumnya, hal ini tidak akan menimbulkan masalah, tetapi berhati-hatilah untuk fitur seperti layar pemetaan ulang tombol.

Trigger MotionEvent KeyEvent
Tombol Pemicu Kiri AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Tombol Kanan AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

Anda harus berhati-hati untuk memverifikasi bahwa game Anda dapat menangani KeyEvent dan MotionEvent untuk mempertahankan kompatibilitas dengan sebanyak mungkin pengontrol, dan bahwa peristiwa diduplikasi.

Pengontrol yang Didukung

Saat menguji, sebaiknya Anda memvalidasi bahwa game Anda berfungsi dengan satu pengontrol di setiap kategori.

  • Gaya Xbox
  • Gaya Nintendo Switch
  • Gaya PlayStation

Anda dapat melakukan pengujian dengan pengontrol pihak pertama atau produsen pihak ketiga yang populer, dan kami umumnya memetakan pengontrol paling populer ke definisi sedekat mungkin.