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

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

כששחקנים מתחברים פיזית או מתאימים אלחוטי בקר משחקים אל במכשירים שלהם מבוססי Android, המערכת מזהה את הבקר באופן אוטומטי כמכשיר לקליטת נתונים, ומתחיל לדווח על אירועי הקלט שלו. המשחק שלך יכול לקבל באירועי הקלט האלה, על ידי הטמעת השיטות הבאות לקריאה חוזרת (callback) בחשבון הפעיל Activity או View במוקד (עליך להטמיע את הקריאות החוזרות עבור Activity או View, אבל לא גם וגם):

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

KeyEvent
אובייקט שמתאר כיוונים אירועים בלחצני החיצים (D-pad) ובקר משחקים. אירועים מרכזיים מלווים קוד מפתח שמציין את הלחצן הספציפי שמופעל, כמו DPAD_DOWN או BUTTON_A. אפשר לקבל את לקוד המפתח getKeyCode() או מהמפתח קריאות חוזרות (callback) של אירועים, כמו onKeyDown().
MotionEvent
אובייקט שמתאר קלט מהג'ויסטיק ומההדק בכתף תנועות חדשות. אירועי תנועה מלווים בקוד פעולה ובקבוצה של ערכי צירים. קוד הפעולה מציין את שינוי המצב שהתרחש למשל ג'ויסטיק שמזיז. ערכי הציר מתארים את המיקום מאפייני תנועה לשליטה פיזית ספציפית, AXIS_X או AXIS_RTRIGGER. אפשר לקבל את קוד הפעולה באמצעות קריאה ל-getAction() וערך הציר בהתקשרות אל getAxisValue().

השיעור הזה מתמקד באופן שבו אפשר להתמודד עם קלט מהסוגים הנפוצים ביותר אמצעי בקרה פיזיים (לחצני בקר משחקים, רפידות כיווניות ג'ויסטיקים) במסך משחק על ידי הטמעת View שיטות להתקשרות חזרה ועיבוד KeyEvent ו-MotionEvent אובייקטים.

איך לוודא שמחובר בקר משחקים

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

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

  • SOURCE_GAMEPAD מציין לפי סוג המקור שמכשיר הקלט כולל לחצנים של בקר משחקים (לדוגמה, BUTTON_A). שימו לב שהמקור הזה type לא מציין במפורש אם בקר המשחק כולל לחצנים בלחצני החיצים (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 (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.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);
        int sources = dev.getSources();

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

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

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

  • ב-Android מגרסה 4.4 (רמת API 19) ואילך, אפשר לקבוע אם קוד מפתח נתמכת בבקר משחקים מחובר באמצעות התקשרות hasKeys(int...)
  • ב-Android 3.1 (רמת API 12) ואילך, ניתן למצוא את כל הצירים הזמינים נתמכת בבקר משחקים מחובר באמצעות קריאה ראשונה getMotionRanges() לאחר מכן, בכל הוחזר אובייקט אחד (InputDevice.MotionRange), קריאה getAxis() כדי לקבל את מזהה הציר שלו.

עיבוד לחיצות על לחצני בקר משחקים

איור 1 מראה איך מערכת Android ממפה ערכי מפתחות וערכי צירים אמצעי הבקרה הזמינים ברוב בקרי המשחקים.

איור 1. פרופיל לבקר משחקים גנרי.

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

קודי מפתח נפוצים שנוצרים על ידי לחיצות על בקר משחקים: BUTTON_A, BUTTON_B, BUTTON_SELECT, ו-BUTTON_START. חלק מהמשחק הבקרים מפעילים גם את קוד המפתח DPAD_CENTER כשלוחצים על המרכז של משטח ה-D-pad. שלך המשחק יכול לבדוק את קוד המפתח באמצעות התקשרות אל getKeyCode() או מקריאות חוזרות של אירועים מרכזיים כמו onKeyDown(), ואם הוא מייצג אירוע שרלוונטי למשחק, לעבד אותו פעולה במשחק. טבלה 1 מפרטת את פעולות המשחק המומלצות לפעולות הנפוצות ביותר לחצני בקר משחקים.

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

פעולה במשחק קוד מפתח לחצן
התחלת המשחק בתפריט הראשי, או השהיה או ביטול השהיה במהלך המשחק BUTTON_START*
הצגת התפריט BUTTON_SELECT* ו-KEYCODE_MENU*
זהה להתנהגות הניווט הקודם של Android שמתוארת עיצוב ניווט מותאמת אישית. KEYCODE_BACK
ניווט חזרה לפריט קודם בתפריט BUTTON_B
אישור הבחירה או ביצוע פעולה ראשית במשחק BUTTON_A והקבוצה DPAD_CENTER

* אסור שהמשחק יסתמך על הלחצנים 'התחלה', 'בחירה' או 'תפריט' לחצנים.

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

קטע הקוד הבא מראה איך אפשר לשנות onKeyDown() עד לשייך את BUTTON_A לחיצות על הלחצן DPAD_CENTER באמצעות פעולה במשחק.

Kotlin

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

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

Java

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

הערה: ב-Android 4.2 (API) ברמה 17 ומטה, המערכת מתייחסת BUTTON_A בתור Android כברירת מחדל, המקש הקודם. אם האפליקציה תומכת במכשירי Android האלה חשוב לטפל BUTTON_A בתור המשחק הראשי פעולה. כדי לבדוק מהי גרסת ה-SDK הנוכחית של Android במכשיר עצמו, קראו את ערך של Build.VERSION.SDK_INT.

עיבוד קלט לחצנים כיווניים

לחצני החיצים (D-pad) הם אמצעי בקרה פיזי נפוץ במשחקים רבים לנאמני מידע. מערכת Android מדווחת על לחצני החיצים למעלה ולמטה בתור AXIS_HAT_Y אירועים עם טווח מ-1.0- (למעלה) ל-1.0 (למטה), ולחצני החיצים LEFT או RIGHT מקישים בתור AXIS_HAT_X אירועים עם טווח של -1.0 (משמאל) ל-1.0 (ימין).

במקום זאת, חלק מהבקרים מדווחים על לחיצות בלחצני החיצים עם קוד מפתח. אם המשחק שלך לגבי לחיצות עם מקשי החיצים, צריך לטפל באירועים בציר הכובע ובלחצני החיצים (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.
            event.source and InputDevice.SOURCE_DPAD != 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.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

אפשר להשתמש בכיתה העוזרת הזו במשחק בכל מקום שרוצים לעבד קלט D-pad (לדוגמה, onGenericMotionEvent() או onKeyDown() קריאות חוזרות (callbacks).

לדוגמה:

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.

תוכלו להשתמש במידע ההיסטורי כדי לעבד את המשחק בצורה מדויקת יותר על סמך קלט הג'ויסטיק. שפת תרגום מאחזרים את הערכים הנוכחיים וההיסטוריים, getAxisValue() או getHistoricalAxisValue(). אפשר גם למצוא את מספר הנתונים נקודות באירוע הג'ויסטיק באמצעות התקשרות getHistorySize()

קטע הקוד הבא מראה איך אפשר לשנות את קריאה חוזרת של 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 אירועים. חשוב להשתמש בכינוי ושני הבקרים נתקעו בקוד.
  • לחיצות על הכתפיים בכתפיים (אבל מספקות קלט חלופי ). לחלק מהבקרים יש כתף שמאלי ואוזן ימין טריגרים. אם הטריגרים האלה קיימים, מערכת Android מדווחת על טריגר שמאלי בלחיצה כאירוע AXIS_LTRIGGER לחיצה על טריגר ימני אירוע AXIS_RTRIGGER. ב-Android 4.3 (רמת API 18), בקר שמפיק AXIS_LTRIGGER מדווח גם על ערך זהה עבור הציר AXIS_BRAKE. אותו עיקרון נכון גם לגבי AXIS_RTRIGGER AXIS_GAS. המערכת של Android מדווחת על כל הטריגרים האנלוגיים לוחץ עם ערך מנורמל מ-0.0 (פורסם) ל-1.0 (בלחיצה מלאה). לא לכל הבקרים יש טריגרים, לכן כדאי לאפשר לשחקנים לבצע אותם פעולות במשחק באמצעות לחצנים אחרים.