تتضمّن وحدات التحكّم نوعَين من الإجراءات:
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يتم عرض لأيMotionEventdispatchKeyEvent. يتم عرضKeyEvent.
التأكّد من توصيل ذراع التحكّم في الألعاب
عند الإبلاغ عن أحداث الإدخال، ستعيد أجهزة Android استخدام المعرّفات نفسها للمفاتيح أو المحاور لأنواع مختلفة من أجهزة الإدخال. على سبيل المثال، يؤدي إجراء على الشاشة التي تعمل باللمس إلى إنشاء حدث
AXIS_X يمثّل الإحداثي X
لسطح اللمس، ولكن يؤدي لوحة الألعاب إلى إنشاء حدث
AXIS_X يمثّل الموضع X
لعصا التحكّم اليسرى. هذا يعني أنّه يجب التحقّق من نوع المصدر لتفسير أحداث الإدخال بشكلٍ سليم.
للتأكّد من أنّ InputDevice متصلة هي ذراع التحكّم في الألعاب، استخدِم الدالة
supportsSource(int):
- يشير نوع المصدر
SOURCE_GAMEPADإلى أنّ جهاز الإدخال يتضمّن أزرار وحدة تحكّم (على سبيل المثال،KEYCODE_BUTTON_A). يُرجى العِلم أنّ نوع المصدر هذا لا يشير بدقة إلى ما إذا كانت ذراع التحكّم في الألعاب تتضمّن أزرار لوحة الاتجاهات، على الرغم من أنّ معظم وحدات التحكّم تتضمّن عادةً عناصر تحكّم في الاتجاهات. - يشير نوع المصدر
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 (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. الملصق الأيسر | 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); } }
معالجة إدخال لوحة الاتجاهات
لوحة الاتجاهات الرباعية، أو لوحة الاتجاهات، هي عنصر تحكّم مادي شائع في العديد من وحدات التحكّم في الألعاب. تسجِّل أجهزة Android الضغطات على لوحة الاتجاهات للأعلى والأسفل على أنّها أحداث AXIS_HAT_Y، مع الإشارة إلى الأعلى بالقيمة -1.0 والإشارة إلى الأسفل بالقيمة 1.0. تسجِّل أجهزة Android الضغطات على لوحة الاتجاهات لليسار أو اليمين على أنّها أحداث AXIS_HAT_X، مع الإشارة إلى اليسار بالقيمة -1.0 والإشارة إلى اليمين بالقيمة 1.0.
بدلاً من ذلك، تسجِّل بعض وحدات التحكّم الضغطات على لوحة الاتجاهات باستخدام رمز مفتاح. إذا كانت لعبتك تهتم بالضغطات على لوحة الاتجاهات، عليك التعامل مع أحداث محور القبعة ورموز مفاتيح لوحة الاتجاهات على أنّها أحداث الإدخال نفسها، كما هو مقترَح في الجدول 2.
الجدول 2. إجراءات اللعبة التلقائية المقترَحة لرموز مفاتيح لوحة الاتجاهات وقيم محور القبعة
| إجراء اللعبة | رمز مفتاح لوحة الاتجاهات | رمز محور القبعة |
|---|---|---|
| تحريك لأعلى | 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);
}
}
يمكنك استخدام هذه الفئة المساعِدة في لعبتك في أي مكان تريد فيه معالجة إدخال لوحة الاتجاهات (على سبيل المثال، في
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()(للعثور على عدد النقاط السابقة في حدث ذراع التحكّم)
يوضِّح مقتطف الرمز التالي كيفية إلغاء معاودة الاتصال
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 (مستوى واجهة برمجة التطبيقات 18) والإصدارات الأحدث، تسجِّل وحدة التحكّم التي تُصدرAXIS_LTRIGGERأيضًا قيمة مطابقة لمحورAXIS_BRAKE. وينطبق الأمر نفسه علىAXIS_RTRIGGERوAXIS_GAS. تسجِّل أجهزة Android جميع الضغطات على حالات التشغيل التناظرية بقيمة عادية من 0.0 (تم رفع حالة التشغيل) إلى 1.0 (تم الضغط على حالة التشغيل بالكامل). - قد تختلف السلوكيات المحدّدة والدعم في البيئات المحاكاة.
قد تختلف المنصّات المحاكاة، مثل ألعاب Google Play،
قليلاً في السلوك استنادًا إلى إمكانات نظام التشغيل المضيف. على سبيل المثال، لا تُصدر بعض وحدات التحكّم التي تُصدر كلاً من أحداث
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، لذا فإنّ |
| وحدات التحكّم بنمط PlayStation
هذه وحدات تحكّم مصمّمة عادةً لعائلة وحدات تحكّم Sony PlayStation* |
تُرسِل وحدات التحكّم هذه MotionEvents
مثل وحدات التحكّم بنمط Xbox، ولكنها تُرسِل أيضًا KeyEvents
مثل وحدات التحكّم بنمط 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 للمساعدة في زيادة التوافق إلى أقصى حد بين عجلات السباق ووحدات التحكّم العادية في الألعاب. بشكلٍ عام، لن يؤدي ذلك إلى حدوث مشاكل، ولكن يُرجى العِلم أنّ ذلك قد يؤثر في ميزات مثل شاشات إعادة ربط المفاتيح.
| Trigger | MotionEvent |
KeyEvent |
|---|---|---|
| حالات التشغيل اليسرى | AXIS_LTRIGGERAXIS_BRAKE
|
KEYCODE_BUTTON_L2
|
| حالات التشغيل اليمنى | AXIS_RTRIGGERAXIS_GAS
|
KEYCODE_BUTTON_R2
|
يجب توخي الحذر للتأكّد من أنّ لعبتك يمكنها التعامل مع كل من KeyEvent وMotionEvent للحفاظ على التوافق مع أكبر عدد ممكن من وحدات التحكّم، ومن إزالة الأحداث المكرّرة.
وحدات التحكّم المتوافقة
عند إجراء الاختبارات، ننصحك بالتأكّد من أنّ لعبتك تعمل مع وحدة تحكّم واحدة في كل فئة.
- نمط Xbox
- نمط Nintendo Switch
- نمط PlayStation
يمكنك إجراء الاختبارات باستخدام وحدات تحكّم من الطرف الأول أو من مصنّعين خارجيين شائعين، ونحن نربط بشكلٍ عام وحدات التحكّم الأكثر شيوعًا بالتعريف بأقرب ما يمكن.