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

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

  • 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 יכולים לצפות לו.

שלט משחק כללי עם קלטים מסומנים, כולל כפתורי החיצים (D-pad), ג&#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. לחצן Bumper ימני
KEYCODE_BUTTON_R1
9. הדק ימני
AXIS_RTRIGGER
10. הדק שמאלי AXIS_LTRIGGER
11. Left Bumper 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), או 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), כדאי להתייחס לאירועים של ציר הכובע ולקודי המקשים של כפתורי החיצים (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) (לדוגמה, בקריאות החוזרות (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, ולהפך.

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

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

הבקרים האלה שולחים MotionEvents כמו Xbox Style Controllers, אבל גם שולחים KeyEvents כמו Switch Style Controllers כשהם לחוצים עד הסוף. בבקרים האלה יש מערכת שונה של גליפים לכפתורים הקדמיים.

* ‫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

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