לבקרים יש שני סוגים של פעולות:
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. באמפר ימני |
KEYCODE_BUTTON_R1 |
|
| 9. הדק ימני |
AXIS_RTRIGGER |
|
| 10. הדק שמאלי | AXIS_LTRIGGER |
|
| 11. לחצן L1 | KEYCODE_BUTTON_L1 |
|
| 12. התחלה | KEYCODE_BUTTON_START |
|
| 13. בחירה | KEYCODE_BUTTON_SELECT |
טיפול בלחיצות על לחצנים
מכיוון שמערכת Android מדווחת על לחיצות על לחצני בקר באופן זהה ללחיצות על לחצני מקלדת, צריך:
- בודקים שהאירוע מגיע מ-
SOURCE_GAMEPAD. - חשוב לוודא שהלחצן מתקבל רק פעם אחת עם
KeyEvent.getRepeatCount(). מערכת Android תשלח אירועים חוזרים של מקשים בדיוק כמו במקרה של לחיצה ארוכה על מקש במקלדת. - כדי לציין שאירוע מסוים מטופל, מחזירים את הערך
true. מעבירים אירועים שלא טופלו אל
superכדי לוודא ששכבות התאימות השונות של Android פועלות בצורה תקינה.Kotlin
class GameView : View { // ... override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { event.apply { var handled = false // make sure we're handling gamepad events if (isFromSource(SOURCE_GAMEPAD)) { // avoid processing the keycode repeatedly if (repeatCount == 0) { when (keyCode) { // handle the "A" button KEYCODE_BUTTON_A -> { handled = true } } // ... } } if (handled) { return true } } return super.onKeyDown(keyCode, event) } }Java
public class GameView extends View { // ... @Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean handled = false; // make sure we're handling gamepad events if (event.isFromSource(SOURCE_GAMEPAD)) { // avoid processing the keycode repeatedly if (event.getRepeatCount() == 0) { switch (keyCode) { case KEYCODE_BUTTON_A: // handle the "A" button handled = true; break; // ... } } // mark this event as handled if (handled) { return true; } } // Always do this instead of "return false" // it allows Android's input compatibility layers to work return super.onKeyDown(keyCode, event); } }
עיבוד קלט של מקשי החצים
משטח הניווט (D-pad) הוא אמצעי בקרה פיזי נפוץ בהרבה בקרי משחקים. מערכת Android מדווחת על לחיצות על החיצים למעלה ולמטה בלחצני הניווט כאירועים מסוג AXIS_HAT_Y, כאשר -1.0 מציין לחיצה על החץ למעלה ו-1.0 מציין לחיצה על החץ למטה. הוא מדווח על לחיצות על לחצן החיצים (D-pad) שמאלה או ימינה כאירועי AXIS_HAT_X, כאשר -1.0 מציין שמאלה ו-1.0 מציין ימינה.
חלק מהבקרים מדווחים על לחיצות על מקשי החיצים באמצעות קוד מקש. אם במשחק שלכם חשובות לחיצות על מקשי החיצים, צריך להתייחס לאירועים של ציר הכובע ולקודי המקשים של מקשי החיצים כאל אותם אירועי קלט, כמו שמומלץ בטבלה 2.
טבלה 2. פעולות ברירת מחדל מומלצות במשחק עבור קודי מקשים של D-pad וערכי ציר של כובע.
| פעולות במשחק | קוד מקש של לחצני החיצים (D-pad) | קוד ציר הכובע |
|---|---|---|
| הזזה למעלה | KEYCODE_DPAD_UP |
AXIS_HAT_Y (לערכים 0 עד -1.0) |
| הזזה למטה | KEYCODE_DPAD_DOWN |
AXIS_HAT_Y (לערכים 0 עד 1.0) |
| הזזה שמאלה | KEYCODE_DPAD_LEFT |
AXIS_HAT_X (לערכים 0 עד -1.0) |
| הזזה ימינה | KEYCODE_DPAD_RIGHT |
AXIS_HAT_X (לערכים 0 עד 1.0) |
בקטע הקוד הבא מוצג מחלקה מסייעת שמאפשרת לבדוק את ערכי הציר והקוד של הכובע מאירוע קלט כדי לקבוע את הכיוון של לחצני החיצים.
Kotlin
class Dpad {
private var directionPressed = -1 // initialized to -1
fun getDirectionPressed(event: InputEvent): Int {
if (!isDpadDevice(event)) {
return -1
}
// If the input event is a MotionEvent, check its hat axis values.
(event as? MotionEvent)?.apply {
// Use the hat axis value to find the D-pad direction
val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)
directionPressed = when {
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
// LEFT and RIGHT direction accordingly.
xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
// UP and DOWN direction accordingly.
yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
else -> directionPressed
}
}
// If the input event is a KeyEvent, check its key code.
(event as? KeyEvent)?.apply {
// Use the key code to find the D-pad direction.
directionPressed = when(event.keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
KeyEvent.KEYCODE_DPAD_CENTER -> Dpad.CENTER
else -> directionPressed
}
}
return directionPressed
}
companion object {
internal const val UP = 0
internal const val LEFT = 1
internal const val RIGHT = 2
internal const val DOWN = 3
internal const val CENTER = 4
fun isDpadDevice(event: InputEvent): Boolean =
// Check that input comes from a device with directional pads.
return event.isFromSource(InputDevice.SOURCE_DPAD)
}
}
Java
public class Dpad {
final static int UP = 0;
final static int LEFT = 1;
final static int RIGHT = 2;
final static int DOWN = 3;
final static int CENTER = 4;
int directionPressed = -1; // initialized to -1
public int getDirectionPressed(InputEvent event) {
if (!isDpadDevice(event)) {
return -1;
}
// If the input event is a MotionEvent, check its hat axis values.
if (event instanceof MotionEvent) {
// Use the hat axis value to find the D-pad direction
MotionEvent motionEvent = (MotionEvent) event;
float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
// LEFT and RIGHT direction accordingly.
if (Float.compare(xaxis, -1.0f) == 0) {
directionPressed = Dpad.LEFT;
} else if (Float.compare(xaxis, 1.0f) == 0) {
directionPressed = Dpad.RIGHT;
}
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
// UP and DOWN direction accordingly.
else if (Float.compare(yaxis, -1.0f) == 0) {
directionPressed = Dpad.UP;
} else if (Float.compare(yaxis, 1.0f) == 0) {
directionPressed = Dpad.DOWN;
}
}
// If the input event is a KeyEvent, check its key code.
else if (event instanceof KeyEvent) {
// Use the key code to find the D-pad direction.
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
directionPressed = Dpad.LEFT;
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
directionPressed = Dpad.RIGHT;
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
directionPressed = Dpad.UP;
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
directionPressed = Dpad.DOWN;
} else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
directionPressed = Dpad.CENTER;
}
}
return directionPressed;
}
public static boolean isDpadDevice(InputEvent event) {
// Check that input comes from a device with directional pads.
return event.isFromSource(InputDevice.SOURCE_DPAD);
}
}
אפשר להשתמש במחלקה הזו במשחק בכל מקום שבו רוצים לעבד קלט של לחצני החיצים (לדוגמה, בקריאות החוזרות (callback) של onGenericMotionEvent() או onKeyDown()).
לדוגמה:
Kotlin
private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
if (Dpad.isDpadDevice(event)) {
when (dpad.getDirectionPressed(event)) {
Dpad.LEFT -> {
// Do something for LEFT direction press
...
return true
}
Dpad.RIGHT -> {
// Do something for RIGHT direction press
...
return true
}
Dpad.UP -> {
// Do something for UP direction press
...
return true
}
...
}
}
// Check if this event is from a joystick movement and process accordingly.
...
}
Java
Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
// Check if this event if from a D-pad and process accordingly.
if (Dpad.isDpadDevice(event)) {
int press = dpad.getDirectionPressed(event);
switch (press) {
case LEFT:
// Do something for LEFT direction press
...
return true;
case RIGHT:
// Do something for RIGHT direction press
...
return true;
case UP:
// Do something for UP direction press
...
return true;
...
}
}
// Check if this event is from a joystick movement and process accordingly.
...
}
עיבוד תנועות הג'ויסטיק
כששחקנים מזיזים ג'ויסטיק בבקרי המשחקים שלהם, מערכת Android מדווחת על MotionEvent שמכיל את קוד הפעולה ACTION_MOVE ואת המיקומים המעודכנים של הצירים של הג'ויסטיק. המשחק יכול להשתמש בנתונים שסופקו על ידי MotionEvent כדי לקבוע אם התרחשה תנועה של ג'ויסטיק שחשובה לו.
שימו לב שאירועי תנועה של ג'ויסטיק עשויים לאגד כמה דגימות של תנועה באובייקט אחד. אובייקט MotionEvent מכיל את המיקום הנוכחי של כל ציר של הג'ויסטיק, וגם כמה מיקומים היסטוריים של כל ציר. כשמדווחים על אירועי תנועה עם קוד הפעולה
ACTION_MOVE (כמו תנועות של ג'ויסטיק), מערכת Android מאגדת את ערכי הציר כדי לייעל את התהליך. הערכים ההיסטוריים של ציר מסוים כוללים את קבוצת הערכים הייחודיים שגילם גדול יותר מהערך הנוכחי של הציר, וחדשים יותר מהערכים שדווחו באירועי תנועה קודמים. פרטים נוספים מופיעים במפרט של השיטה ב-MotionEvent.
כדי להציג בצורה מדויקת את התנועה של אובייקט במשחק על סמך קלט של ג'ויסטיק, אפשר להשתמש במידע היסטורי שסופק על ידי אובייקטים מסוג MotionEvent.
אפשר לאחזר ערכים נוכחיים והיסטוריים באמצעות השיטות הבאות:
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 מוחלפים, כך שהלחצן |
| בקרים בסגנון פלייסטיישן
הבקרים האלה מיועדים בדרך כלל לקונסולות של סוני פלייסטיישן* . |
הבקרים האלה שולחים MotionEvent, כמו בקרים בסגנון Xbox, אבל גם שולחים KeyEvent, כמו בקרים בסגנון Switch כשהם לחוצים עד הסוף. |
הבקרים האלה משתמשים בסדרה שונה של גליפים ללחצני הפנים. |
* Microsoft, Xbox ו-Windows הם סימנים מסחריים רשומים של Microsoft; Nintendo Switch הוא סימן מסחרי רשום של Nintendo of America Inc.; PlayStation הוא סימן מסחרי רשום של Sony Interactive Entertainment Inc.
הבחנה בין לחצני הפעלה
חלק מהבקרים שולחים את האירועים AXIS_LTRIGGER ו-AXIS_RTRIGGER, חלקם שולחים את האירועים KEYCODE_BUTTON_L2 ו-KEYCODE_BUTTON_R2, ואחרים שולחים את כל האירועים האלה בהתאם ליכולות החומרה שלהם. כדי להשיג תאימות מקסימלית, חשוב לתמוך בכל האירועים האלה.
כל הבקרים ששולחים AXIS_LTRIGGER ישלחו גם AXIS_BRAKE, וכך גם לגבי AXIS_RTRIGGER ו-AXIS_GAS, כדי למקסם את התאימות בין הגה מירוצים לבין בקרי משחקים רגילים. בדרך כלל זה לא גורם לבעיות, אבל חשוב לשים לב לזה בתכונות כמו מסכים של מיפוי מחדש של מקשים.
| טריגר | MotionEvent |
KeyEvent |
|---|---|---|
| הדק שמאלי | AXIS_LTRIGGERAXIS_BRAKE
|
KEYCODE_BUTTON_L2
|
| הדק ימני | AXIS_RTRIGGERAXIS_GAS
|
KEYCODE_BUTTON_R2
|
כדי לשמור על תאימות למספר רב ככל האפשר של בקרי משחק, חשוב לוודא שהמשחק יכול לטפל גם ב-KeyEvent וגם ב-MotionEvent, ושהאירועים לא משוכפלים.
בקרי משחקים נתמכים
במהלך הבדיקה, מומלץ לוודא שהמשחק פועל עם בקר אחד בכל אחת מהקטגוריות.
- סגנון Xbox
- סגנון Nintendo Switch
- סגנון PlayStation
אתם יכולים לבדוק את התכונה באמצעות בקרי צד ראשון או בקרי צד שלישי פופולריים, ובדרך כלל אנחנו ממפים את הבקרים הפופולריים ביותר להגדרה בצורה הכי מדויקת שאפשר.