ברמת המערכת, Android מדווחת על קודי אירועי קלט ממכשירי בקרת משחקים בתור קודי מפתחות וערכים של צירים ב-Android. במשחק, תוכלו לקבל את הקודים והערכים האלה ולהמיר אותם לפעולות ספציפיות במשחק.
כששחקנים מחברים פיזית או מתאימים באופן אלחוטי שלט משחק למכשירים שלהם עם Android, המערכת מזהה את השלט באופן אוטומטי כמכשיר קלט ומתחילה לדווח על אירועי הקלט שלו. המשחק יכול לקבל את אירועי הקלט האלה על ידי הטמעת השיטות הבאות לקריאה חוזרת (callback) ב-Activity
הפעיל או ב-View
המודגש (צריך להטמיע את הקריאות החוזרות ל-Activity
או ל-View
, אבל לא לשניהם):
- החל מ-
Activity
:dispatchGenericMotionEvent(android.view. MotionEvent)
קריאה לעיבוד אירועי תנועה כלליים כמו תנועות בג'ויסטיק.
dispatchKeyEvent(android.view.KeyEvent)
הקריאה הזו נקראת כדי לעבד אירועי מפתח, כמו לחיצה או שחרור של לחצן במשטח המשחק או בלחצן D-pad.
- מאת
View
:onGenericMotionEvent(android.view.MotionEvent)
הקריאה מתבצעת כדי לעבד אירועי תנועה כלליים, כמו תנועות של ג'ויסטיק.
onKeyDown(int, android.view.KeyEvent)
הקריאה הזו מתבצעת כדי לעבד לחיצה על מקש פיזי, כמו לחצן ב-D-pad או ב-gamepad.
onKeyUp(int, android.view.KeyEvent)
הקריאה מתבצעת כדי לעבד שחרור של מקש פיזי, כמו לחצן ב-D-pad או ב-gamepad.
הגישה המומלצת היא לתעד את האירועים מהאובייקט הספציפי של View
שאליו המשתמש יוצר אינטראקציה.
כדי לקבל מידע על סוג אירוע הקלט שהתקבל, בודקים את האובייקטים הבאים שסופקו על ידי הקריאות החוזרות:
KeyEvent
- אובייקט שמתאר אירועים של לחצנים במשטח הכיוונים (D-pad) ובג'וי פד. אירועים מרכזיים מלווים בקוד מפתח שמציין את הלחצן הספציפי שהופעל, למשל
DPAD_DOWN
אוBUTTON_A
. אפשר לקבל את קוד המפתח באמצעות קריאה ל-getKeyCode()
או מקריאות חזרה של אירועי מפתח, כמוonKeyDown()
. MotionEvent
- אובייקט שמתאר קלט מהתנועות של מוט ההיגוי וממנוף הכתף. אירועי תנועה מלווים בקוד פעולה ובקבוצה של ערכי צירים. קוד הפעולה מציין את שינוי המצב שהתרחש, למשל ג'ויסטיק שהועבר. ערכי הציר מתארים את המיקום ומאפייני תנועה אחרים של אמצעי בקרה פיזי ספציפי, כמו
AXIS_X
אוAXIS_RTRIGGER
. כדי לקבל את קוד הפעולה, צריך להפעיל את הפונקציהgetAction()
, ואת ערך הציר באמצעות הפונקציהgetAxisValue()
.
בשיעור הזה נתמקד באופן שבו אפשר להתמודד עם קלט מהסוגים הנפוצים ביותר של אמצעי בקרה פיזיים (לחצני בקר משחקים, רפידות כיוון וג'ויסטיקים) במסך משחק על ידי הטמעת שיטות הקריאה החוזרת (callback) ועיבוד האובייקטים KeyEvent
ו-MotionEvent
שצוינו למעלה.View
מוודאים שבקר המשחק מחובר
כשמדווחים על אירועי קלט, מערכת Android לא מבדילה בין אירועים שהגיעו ממכשיר שאינו בקר משחקים לבין אירועים שהגיעו מבקר משחקים. לדוגמה, פעולה במסך מגע יוצרת אירוע AXIS_X
שמייצג את קואורדינטת ה-X של משטח המגע, אבל ג'ויסטיק יוצר אירוע AXIS_X
שמייצג את המיקום ה-X של הג'ויסטיק. אם המשחק שלכם מטפל בקלט של בקר משחקים, תחילה עליכם לוודא שאירוע הקלט מגיע מסוג מקור רלוונטי.
כדי לוודא שמכשיר קלט מחובר הוא שלט לגיימינג, צריך להפעיל את הפונקציה getSources()
כדי לקבל שדה ביט משולב של סוגי מקורות הקלט הנתמכים במכשיר הזה. לאחר מכן תוכלו לבדוק אם השדות הבאים מוגדרים:
- סוג המקור של
SOURCE_GAMEPAD
מציין שמכשיר הקלט כולל לחצני בקר משחקים (לדוגמה,BUTTON_A
). שימו לב שסוג המקור הזה לא מציין במפורש אם בקר המשחקים כולל לחצני D-pad, אבל לרוב הגיימפאד יש לחצני כיוון. - סוג מקור של
SOURCE_DPAD
מציין שבמכשיר לקליטת נתונים יש לחצנים בלחצני החיצים (לדוגמה,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 ואילך), כדי למצוא את כל הצירים הזמינים שנתמכים ב-game controller מחובר, קודם צריך להפעיל את הפונקציה
getMotionRanges()
. לאחר מכן, בכל אובייקטInputDevice.MotionRange
שמוחזר, צריך לבצע קריאה ל-getAxis()
כדי לקבל את מזהה הציר.
עיבוד לחיצות על לחצני בקר המשחקים
באיור 1 מוצג איך Android ממפה קודי מפתחות וערכים של צירים לפקדים הפיזיים ברוב בקרי המשחקים.
נכסי היתרונות המרכזיים שמופיעים באיור מתייחסים לפרטים הבאים:
קודי מפתח נפוצים שנוצרים על ידי לחיצות על לחצני משחקי הווידאו כוללים את BUTTON_A
, BUTTON_B
, BUTTON_SELECT
ו-BUTTON_START
. בחלק ממכשירי הבקרה למשחקים, קוד המקש DPAD_CENTER
מופעל גם כשלוחצים על מרכז מוט האחיזה של D-pad. המשחק יכול לבדוק את קוד המפתח באמצעות קריאה ל-getKeyCode()
או מתוך קריאות חזרה (callbacks) של אירועי מפתח, כמו onKeyDown()
. אם הקוד מייצג אירוע שרלוונטי למשחק, אפשר לעבד אותו כפעולה במשחק. בטבלה 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
כמקש Back של Android כברירת מחדל. אם האפליקציה תומכת בגרסאות Android האלה, חשוב להתייחס ל-BUTTON_A
כפעולת המשחק הראשית. כדי לקבוע את הגרסה הנוכחית של Android SDK במכשיר, עיינו בערך Build.VERSION.SDK_INT
.
עיבוד קלט לחצני החיצים
לחצן הכיוונים (D-pad) בן 4 הכיוונים (D-pad) הוא אמצעי בקרה פיזי נפוץ בבקרים רבים
במשחקים. ב-Android מדווחים על לחצני החיצים למעלה ולמטה בתור
AXIS_HAT_Y
אירועים עם טווח
מ-1.0 (למעלה) ל-1.0 (למטה), ולחצני החיצים (D-pad) LEFT או RIGHT לוחץ על
AXIS_HAT_X
עם טווח בין -1.0
(שמאל) ל-1.0 (ימין).
בחלק מהשלטים, במקום זאת, דיווח על לחיצות על D-pad מתבצע באמצעות קוד מפתח. אם במשחק שלכם חשוב לדעת על לחיצות על D-pad, עליכם להתייחס לאירועים של ציר הכובע ולקודי המקשים של D-pad כאל אותם אירועי קלט, כפי שמומלץ בטבלה 2.
פעולה במשחק | קוד מקש של לוח החיצים (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. 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()
).
לדוגמה:
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 נמצא באזור השטוח, צריך להעביר את השלט רחוק למצב מנוחה (כלומר, ללא תנועה בשני הצירים).
בקטע הקוד הבא מוצגת שיטה מסייעת שמחשבת את התנועה לאורך כל ציר. מפעילים את ה-helper הזה ב-method 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_
, ויכול להיות שהתמיכה בחלק מהבקרים תהיה חסרה לחלוטין.