לבקרים יש שני סוגים של פעולות:
KeyEventמשמש לכל לחצן עם מצב בינארי של 'מופעל' ו'מושבת'-
MotionEventמשמש לכל ציר שמחזיר טווח ערכים. לדוגמה, -1 עד 1 עבור מקלות אנלוגיים או 0 עד 1 עבור הדקים אנלוגיים.
אפשר לקרוא את הקלט הזה מView שכולל focus.
- גויסו
onGenericMotionEventעבורMotionEvent. onKeyDownו-onKeyUpמוגדלים עבורKeyEventכשלוחצים על הלחצנים ומשחררים אותם.
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.
- גויסו
dispatchGenericMotionEventמתוךMotionEvent dispatchKeyEvent. גיוס הכספים מתבצע עבור כלKeyEvent.
אימות של חיבור של שלט משחק
כשמדווחים על אירועי קלט, מערכת 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 יכולים לצפות לו.
בטבלה מפורטים השמות והסוגים של אירועים רגילים לבקרי משחקים. רשימה מלאה של האירועים זמינה במאמר וריאציות נפוצות. המערכת שולחת אירועים מסוג 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.
אפשר לאחזר ערכים נוכחיים והיסטוריים באמצעות השיטות הבאות:
getAxisValue()getHistoricalAxisValue()-
getHistorySize()(כדי למצוא את מספר הנקודות ההיסטוריות באירוע של הג'ויסטיק)
בקטע הקוד הבא מוצג איך אפשר לבטל את הקריאה החוזרת (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, כך שהלחצן |
| בקרים בסגנון פלייסטיישן
הבקרים האלה מיועדים בדרך כלל לקונסולות של 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_LTRIGGERAXIS_BRAKE
|
KEYCODE_BUTTON_L2
|
| הדק ימני | AXIS_RTRIGGERAXIS_GAS
|
KEYCODE_BUTTON_R2
|
חשוב לוודא שהמשחק יכול להתמודד עם KeyEvent וגם עם MotionEvent כדי לשמור על תאימות למספר רב ככל האפשר של שלטים, ולוודא שהאירועים לא משוכפלים.
בקרים נתמכים
במהלך הבדיקה, מומלץ לוודא שהמשחק פועל עם בקר אחד בכל אחת מהקטגוריות.
- סגנון Xbox
- סגנון Nintendo Switch
- סגנון PlayStation
אפשר לבדוק באמצעות בקרי צד ראשון או יצרנים פופולריים של צד שלישי. בדרך כלל אנחנו ממפים את הבקרים הפופולריים ביותר להגדרה בצורה הכי מדויקת שאפשר.