טיפול בפעולות של השלט רחוק

לבקרים יש שני סוגים של פעולות:

  • KeyEvent משמש לכל לחצן עם מצב בינארי של 'מופעל' ו'מושבת'
  • MotionEvent משמש לכל ציר שמחזיר טווח ערכים. לדוגמה, -1 עד 1 עבור מקלות אנלוגיים או 0 עד 1 עבור הדקים אנלוגיים.

אפשר לקרוא את הקלט הזה מView עם 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);
}

במקרה הצורך, אפשר לקרוא אירועים ישירות מ-Activity.

אימות של חיבור בקר משחק

כשמדווחים על אירועי קלט, מערכת Android משתמשת מחדש באותם מזהים של מקשים או צירים עבור סוגים שונים של מכשירי קלט. לדוגמה, פעולה במסך מגע יוצרת אירוע AXIS_X שמייצג את קואורדינטת ה-X של משטח המגע, אבל גיימפד יוצר אירוע AXIS_X שמייצג את מיקום ה-X של הג'ויסטיק השמאלי. כלומר, אתם צריכים לבדוק את סוג המקור כדי לפרש נכון את אירועי הקלט.

כדי לוודא שInputDevice מחובר הוא שלט לגיימינג, משתמשים בפונקציה supportsSource(int):

  • סוג המקור SOURCE_GAMEPAD מציין שלמכשיר הקלט יש לחצני בקר (לדוגמה, KEYCODE_BUTTON_A). הערה: סוג המקור הזה לא מציין באופן חד-משמעי אם לבקר המשחקים יש לחצני D-pad, למרות שברוב הבקרים יש בדרך כלל אמצעי בקרה לכיוונים.
  • סוג המקור SOURCE_DPAD מציין שלמכשיר הקלט יש לחצני D-pad (לדוגמה, DPAD_UP).
  • סוג המקור SOURCE_JOYSTICK מציין שלמכשיר הקלט יש ג'ויסטיקים אנלוגיים (לדוגמה, ג'ויסטיק שמתעד תנועות לאורך AXIS_X ו-AXIS_Y).

בקטע הקוד הבא מוצגת מתודה מסייעת שמאפשרת לבדוק אם מכשירי הקלט המחוברים הם שלטים לגיימינג. אם כן, השיטה מאחזרת את מזהי המכשירים של בקרי המשחקים. לאחר מכן תוכלו לשייך כל מזהה מכשיר לשחקן במשחק ולעבד את פעולות המשחק של כל שחקן מחובר בנפרד. מידע נוסף על תמיכה בכמה בקרי משחקים שמחוברים בו-זמנית לאותו מכשיר עם 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.

בקר משחק כללי עם כניסות מסומנות, כולל לחצני חיצים, ג&#39;ויסטיקים ולחצנים
איור 1. פרופיל של בקר משחקים כללי.

בטבלה מפורטים השמות והסוגים של אירועים רגילים עבור בקרי משחקים. רשימה מלאה של האירועים מופיעה במאמר וריאציות נפוצות. המערכת שולחת אירועים מסוג MotionEvent דרך onGenericMotionEvent ואירועים מסוג KeyEvent דרך onKeyDown ו-onKeyUp.

קלט מהבקר KeyEvent MotionEvent
1. לחצני החיצים
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. לחצן L1 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 מדווחת על לחיצות על החיצים למעלה ולמטה בלחצני הניווט כאירועים מסוג AXIS_HAT_Y, כאשר ‎-1.0 מציין לחיצה על החץ למעלה ו-1.0 מציין לחיצה על החץ למטה. הוא מדווח על לחיצות על לחצן החיצים (D-pad) שמאלה או ימינה כאירועי AXIS_HAT_X, כאשר ‎-1.0 מציין שמאלה ו-‎1.0 מציין ימינה.

חלק מהבקרים מדווחים על לחיצות על מקשי החיצים באמצעות קוד מקש. אם במשחק שלכם חשובות לחיצות על מקשי החיצים, צריך להתייחס לאירועים של ציר הכובע ולקודי המקשים של מקשי החיצים כאל אותם אירועי קלט, כמו שמומלץ בטבלה 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)

בקטע הקוד הבא מוצג מחלקה מסייעת שמאפשרת לבדוק את ערכי הציר והקוד של הכובע מאירוע קלט כדי לקבוע את הכיוון של לחצני החיצים.

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

אפשר להשתמש במחלקה הזו במשחק בכל מקום שבו רוצים לעבד קלט של לחצני החיצים (לדוגמה, בקריאות החוזרות (callback) של 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.

אפשר לאחזר ערכים נוכחיים והיסטוריים באמצעות השיטות הבאות:

בקטע הקוד הבא מוצג איך אפשר לבטל את שיטת הקריאה החוזרת (callback) של 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_*TRIGGER או KEYCODE_BUTTON_*2, או את שניהם. במקרה של הטריגר השמאלי, זה יהיה AXIS_LTRIGGER ו-KEYCODE_BUTTON_L2. הטריגר הנכון יהיה AXIS_RTRIGGER ו- KEYCODE_BUTTON_R2. אירועי ציר מתרחשים רק אם הטריגר פולט טווח ערכים בין 0 ל-1, ובחלק מהבקרים עם פלט אנלוגי נפלטות גם פקודות של לחצנים בנוסף לפקודות של ציר. כדי שהמשחקים יהיו תואמים לכל בקרי המשחק הנפוצים, הם צריכים לתמוך באירועים AXIS_ וגם באירועים KEYCODE_BUTTON_. עם זאת, אם בקר מדווח על שני האירועים, מומלץ להשתמש באירוע שהכי מתאים למשחק. ב-Android 4.3 (רמת API‏ 18) ואילך, בקר שמפיק את הערך AXIS_LTRIGGER מדווח גם על ערך זהה עבור הציר AXIS_BRAKE. אותו עיקרון חל על AXIS_RTRIGGER ועל AXIS_GAS. מערכת Android מדווחת על כל הלחיצות על טריגר אנלוגי עם ערך מנורמל מ-0.0 (שחרור) עד 1.0 (לחיצה מלאה).
  • התנהגויות ספציפיות ותמיכה עשויות להיות שונות בסביבות מדומה. ההתנהגות של פלטפורמות שמופעלות באמצעות אמולציה, כמו Google Play Games, עשויה להיות שונה מעט בהתאם ליכולות של מערכת ההפעלה המארחת. לדוגמה, חלק מהבקרים שפולטים אירועים של AXIS_ ושל KEYCODE_BUTTON_ פולטים רק אירועים של AXIS_, ויכול להיות שחסר תמיכה בחלק מהבקרים.

וריאציות נפוצות

מערכת Android תומכת במגוון רחב של בקרי משחק, ולכן יכול להיות שלא ברור איך לבנות ולבדוק את המשחק כדי לוודא שהוא פועל ללא באגים בקרב בסיס השחקנים. למרות המגוון הזה, יצרני בקרים ברחבי העולם נוטים להקפיד על שלושה סגנונות שונים של בקרים. חלק מהמכשירים מספקים מתגים בחומרה למעבר בין שניים או יותר מהמצבים האלה.

זה אומר שאתם יכולים לבצע בדיקות עם שלושה בקרים בלבד בצוות הפיתוח, ולהיות בטוחים שאפשר לשחק במשחק בלי להשתמש ברשימות של בקרים מותרים ובקרים אסורים.

סוגים נפוצים של בקרים

הסגנון הנפוץ ביותר של בקרים הוא חיקוי של הפריסות של קונסולות משחקים פופולריות. השינוי הזה הוא גם אסתטי, בתוויות של הלחצנים ובפריסה, וגם פונקציונלי, כי הוא משפיע על האירועים שמופעלים. בבקרים עם מתגים פיזיים למעבר בין סוגים שונים של קונסולות, האירועים שהם שולחים ישתנו, ולעתים קרובות גם פריסת הלחצנים הלוגית שלהם.

במהלך הבדיקה, מומלץ לוודא שהמשחק פועל עם בקר אחד בכל אחת מהקטגוריות. אתם יכולים לבדוק באמצעות בקרי צד ראשון או יצרנים פופולריים של צד שלישי. בדרך כלל, אנחנו ממפים את הבקרים הפופולריים ביותר לההגדרה שלמעלה כמיטב יכולתנו.

סוג הבקר הבדלים בהתנהגות תוויות של וריאציות
בקרים בסגנון Xbox

אלה בקרים שמיועדים בדרך כלל לפלטפורמות Microsoft Xbox ו-Windows* .

הבקרים האלה תואמים לקבוצת התכונות שמפורטת במאמר עיבוד קלט של בקרים הלחצנים L2/R2 בשלטים האלה מסומנים כ-LT/RT
בקרים בסגנון Switch

בדרך כלל הבקרים האלה מיועדים לקונסולות Nintendo Switch* ‎.

הבקרים האלה שולחים את KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent הלחצנים L2/R2 בשלטים האלה מסומנים כ-ZL/ZR.

בנוסף, בבקרי המשחקים האלה הלחצנים A ו-B והלחצנים X ו-Y מוחלפים, כך שהלחצן KEYCODE_BUTTON_A הוא הלחצן שמסומן B ולהפך.

בקרים בסגנון פלייסטיישן

הבקרים האלה מיועדים בדרך כלל לקונסולות של סוני פלייסטיישן* .

הבקרים האלה שולחים MotionEvent, כמו בקרים בסגנון Xbox, אבל גם שולחים KeyEvent, כמו בקרים בסגנון Switch כשהם לחוצים עד הסוף. הבקרים האלה משתמשים בסדרה שונה של גליפים ללחצני הפנים.

* ‫Microsoft,‏ Xbox ו-Windows הם סימנים מסחריים רשומים של Microsoft; ‫Nintendo Switch הוא סימן מסחרי רשום של Nintendo of America Inc.; ‫PlayStation הוא סימן מסחרי רשום של Sony Interactive Entertainment Inc.

הבחנה בין לחצני הפעלה

חלק מהבקרים שולחים את האירועים AXIS_LTRIGGER ו-AXIS_RTRIGGER, חלקם שולחים את האירועים KEYCODE_BUTTON_L2 ו-KEYCODE_BUTTON_R2, ואחרים שולחים את כל האירועים האלה בהתאם ליכולות החומרה שלהם. כדי להשיג תאימות מקסימלית, חשוב לתמוך בכל האירועים האלה.

כל הבקרים ששולחים AXIS_LTRIGGER ישלחו גם AXIS_BRAKE, וכך גם לגבי AXIS_RTRIGGER ו-AXIS_GAS, כדי למקסם את התאימות בין הגה מירוצים לבין בקרי משחקים רגילים. בדרך כלל זה לא גורם לבעיות, אבל חשוב לשים לב לזה בתכונות כמו מסכים של מיפוי מחדש של מקשים.

טריגר MotionEvent KeyEvent
הדק שמאלי AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
הדק ימני AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

כדי לשמור על תאימות למספר רב ככל האפשר של בקרי משחק, חשוב לוודא שהמשחק יכול לטפל גם ב-KeyEvent וגם ב-MotionEvent, ושהאירועים לא משוכפלים.

בקרי משחקים נתמכים

במהלך הבדיקה, מומלץ לוודא שהמשחק פועל עם בקר אחד בכל אחת מהקטגוריות.

  • סגנון Xbox
  • סגנון Nintendo Switch
  • סגנון PlayStation

אתם יכולים לבדוק את התכונה באמצעות בקרי צד ראשון או בקרי צד שלישי פופולריים, ובדרך כלל אנחנו ממפים את הבקרים הפופולריים ביותר להגדרה בצורה הכי מדויקת שאפשר.