على مستوى النظام، يُبلغ Android عن رموز أحداث الإدخال من أجهزة تحكّم الألعاب ك رموز مفاتيح Android وقيم المحاور. في لعبتك، يمكنك الحصول على هذه الرموز والقيم وتحويلها إلى إجراءات محددة داخل اللعبة.
عندما يربط اللاعبون وحدة تحكّم في الألعاب بشكلٍ مباشر أو يقرنونها لاسلكيًا
بأجهزتهم التي تعمل بنظام التشغيل Android، يرصد النظام تلقائيًا وحدة التحكّم
كجهاز إدخال ويبدأ في الإبلاغ عن أحداث الإدخال. يمكن للعبة تلقّي
أحداث الإدخال هذه من خلال تنفيذ طرق الاستدعاء التالية في
Activity
النشط أوView
الذي تم التركيز عليه (يجب
تنفيذ عمليات الاستدعاء إما لActivity
أو
View
، ولكن ليس كليهما):
- من
Activity
:dispatchGenericMotionEvent(android.view. MotionEvent)
يُستخدَم لمعالجة أحداث الحركة العامة، مثل حركات ذراع التحكّم.
dispatchKeyEvent(android.view.KeyEvent)
يتمّ استدعاؤه لمعالجة الأحداث الرئيسية، مثل الضغط على أحد أزرار لوحة الألعاب أو زرّ لوحة التوجيه أو تحريره.
- من
View
:onGenericMotionEvent(android.view.MotionEvent)
يتم استدعاؤه لمعالجة أحداث الحركة العامة، مثل حركات عصا التحكم.
onKeyDown(int, android.view.KeyEvent)
يُقصد بها الضغط على مفتاح مادي، مثل جهاز التحكم في الألعاب أو أزرار لوحة التحكّم.
onKeyUp(int, android.view.KeyEvent)
يتم استدعاؤه لمعالجة تحرير مفتاح فعلي، مثل وحدة تحكّم في الألعاب أو زر لوحة التوجيه.
والطريقة المقترحة هي تسجيل الأحداث من
عنصر View
المحدّد الذي يتفاعل معه المستخدم.
تحقّق من العناصر التالية المقدَّمة من وظائف الاستدعاء للحصول على معلومات
عن نوع حدث الإدخال الذي تم تلقّيه:
KeyEvent
- عنصر يصف أحداث keypad (D-pad) وزرّات لوحة الألعاب تَترافق الأحداث الرئيسية مع
رمز مفتاح يشير إلى الزر المحدّد الذي تم تفعيله، مثل
DPAD_DOWN
أوBUTTON_A
. يمكنك الحصول على رمز المفتاح من خلال استدعاءgetKeyCode()
أو من استدعاءات الأحداث الرئيسية مثلonKeyDown()
. MotionEvent
- عبارة عن كائن يصف المدخلات من الحركات التي تحركها
ذراع التحكم والكتف. تَترافق أحداث الحركة مع رمز إجراء ومجموعة من
قيم المحاور. يحدِّد رمز الإجراء تغيير الحالة الذي حدث، مثل تحريك ذراع تحكم. تصف قيم المحاور الموضع وسمات التحرك الأخرى لعنصر تحكّم مادي محدّد، مثل
AXIS_X
أوAXIS_RTRIGGER
. يمكنك الحصول على رمز الإجراء عن طريق استدعاءgetAction()
وقيمة المحور من خلال استدعاءgetAxisValue()
.
يركز هذا الدرس على كيفية معالجة الإدخال من الأنواع الأكثر شيوعًا من
عناصر التحكّم المادية (أزرار لوحة الألعاب ولوحات التوجيه ومقبض التوجيه) في شاشة اللعبة من خلال تنفيذ مثيل View
المذكور أعلاه ومعالجة كائنَي KeyEvent
وMotionEvent
.
التأكّد من توصيل ذراع التحكّم في الألعاب
عند الإبلاغ عن أحداث الإدخال، لا يميز نظام التشغيل Android
بين الأحداث الواردة من جهاز غير ذراع تحكّم في الألعاب والأحداث الواردة
من ذراع تحكّم في الألعاب. على سبيل المثال، يُنشئ إجراء الشاشة التي تعمل باللمس حدثًا
AXIS_X
يمثّل الإحداثي x
لسطح اللمس، ولكنّ ذراع التحكم يُنشئ حدثًا
AXIS_X
يمثّل موضع x لذراع التحكم. إذا كانت لعبتك تهتم بمعالجة إدخالات وحدة التحكم في الألعاب، يجب أولاً التحقق من أن حدث الإدخال يأتي من نوع مصدر ذي صلة.
للتأكّد من أنّ جهاز الإدخال المتصل هو جهاز تحكّم في الألعاب، اتصل بالخدمة
getSources()
للحصول على حقل بتات مجمّع لأنواع
مصادر الإدخال المتوافقة على هذا الجهاز. يمكنك بعد ذلك اختبار ما إذا كانت
الحقول التالية قد تم ضبطها:
- يشير نوع المصدر
SOURCE_GAMEPAD
إلى أنّ جهاز الإدخال يحتوي على أزرار جهاز تحكّم في الألعاب (على سبيل المثال،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 (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 (المستوى 19 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك تحديد ما إذا كان رمز المفتاح متوافقًا مع ذراع التحكّم في الألعاب المرتبطة من خلال طلب الرمز
hasKeys(int...)
. - في الإصدار 3.1 من نظام التشغيل Android (المستوى 12 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك العثور على جميع المحاور المتاحة
المتوافقة مع جهاز تحكّم في الألعاب متصل من خلال استدعاء
getMotionRanges()
أولاً. بعد ذلك، في كل عنصرInputDevice.MotionRange
يتم إرجاعه، يمكنك استدعاءgetAxis()
للحصول على معرّف محوره.
معالجة الضغطات على أزرار جهاز التحكّم في الألعاب
يوضح الشكل 1 كيف يربط Android الرموز الرئيسية وقيم المحاور لعناصر التحكم الفعلية في معظم أذرع الألعاب.
تشير وسائل الشرح في الشكل إلى ما يلي:
تتضمّن رموز المفاتيح الشائعة التي يتم إنشاؤها عند الضغط على أزرار جهاز التحكّم BUTTON_A
وBUTTON_B
وBUTTON_SELECT
وBUTTON_START
. تؤدي بعض وحدات التحكّم في الألعاب أيضًا إلى تشغيل رمز المفتاح DPAD_CENTER
عند الضغط على وسط شريط التمرير في لوحة التحكّم. يمكن لتطبيق
اللعبة فحص رمز المفتاح من خلال استدعاء getKeyCode()
أو من خلال عمليات الاستدعاء للأحداث الرئيسية، مثل
onKeyDown()
،
وإذا كان يمثّل حدثًا ذا صلة بتطبيقك، يمكنك معالجته كأحد
إجراءات اللعبة. يسرد الجدول 1 إجراءات الألعاب المقترَحة لأزرار gamepad الأكثر شيوعًا.
أنشطة الألعاب | رمز مفتاح الزر |
---|---|
بدء اللعبة في القائمة الرئيسية أو إيقافها مؤقتًا أو إعادة تشغيلها أثناء اللعب | BUTTON_START * |
عرض القائمة | BUTTON_SELECT *
وKEYCODE_MENU * |
هذا الخيار مماثل لسلوك التنقّل السابق في نظام Android رجوع كما هو موضّح في دليل التصميم الخاص بالتنقل. | KEYCODE_BACK |
الرجوع إلى عنصر سابق في إحدى القوائم | BUTTON_B |
تأكيد الاختيار أو تنفيذ الإجراء الأساسي في اللعبة | BUTTON_A وDPAD_CENTER |
* يجب ألا تعتمد لعبتك على توفّر أزرار Start (ابدأ) أو Select (اختيار) أو Menu (قائمة).
نصيحة: يمكنك توفير شاشة تهيئة في لعبتك للسماح للمستخدمين بتخصيص تعيين وحدة التحكم في الألعاب لإجراءات اللعبة.
يوضّح المقتطف التالي كيفية إلغاء
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; } }
ملاحظة: في الإصدار 4.2 من Android (المستوى 17 لواجهة برمجة التطبيقات) والإصدارات الأقدم، يتعامل النظام تلقائيًا مع رمز BUTTON_A
على أنّه مفتاح رجوع في Android. إذا كان تطبيقك متوافقًا مع
إصدارات Android هذه، احرص على التعامل مع
BUTTON_A
على أنّه خطوة التحرك الأساسية في اللعبة. لتحديد الإصدار الحالي من حزمة تطوير البرامج (SDK) لنظام التشغيل Android على الجهاز، اطّلِع على قيمة Build.VERSION.SDK_INT
.
معالجة الإدخال من لوحة الاتجاهات
لوحة التوجيه ذات الاتجاهات الأربعة (D-pad) هي عنصر تحكّم شائع في العديد من أدوات التحكّم في الألعاب. يُبلغ نظام التشغيل Android عن الضغط على زرَّي السهم المتّجه للأعلى أو للأسفل في لوحة التحكّم على أنّه حدث
AXIS_HAT_Y
بمدى
من -1.0 (للأعلى) إلى 1.0 (للأسفل)، ويُبلغ عن الضغط على زرَّي السهم المتّجه لليسار أو لليمين في لوحة التحكّم على أنّه حدث
AXIS_HAT_Y
بمدى من -1.0
(لليسار) إلى 1.0 (لليمين).
وبدلاً من ذلك، تُبلغ بعض وحدات التحكّم عن الضغطات على لوحة التحكّم باستخدام رمز مفتاح. إذا كانت لعبتك تهتم بالضغط على لوحة التوجيه، عليك التعامل مع أحداث محور القبّعة ورموز مفاتيح لوحة التوجيه كأحداث الإدخال نفسها، كما هو مقترَح في الجدول 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) |
يعرض مقتطف الرمز التالي فئة مساعدة تتيح لك التحقّق من قيم محور hat ورمز المفتاح من حدث إدخال لتحديد اتجاه لوحة D-pad.
Kotlin
class Dpad { private var directionPressed = -1 // initialized to -1 fun getDirectionPressed(event: InputEvent): Int { if (!isDpadDevice(event)) { return -1 } // If the input event is a MotionEvent, check its hat axis values. (event as? MotionEvent)?.apply { // Use the hat axis value to find the D-pad direction val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X) val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y) directionPressed = when { // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad // LEFT and RIGHT direction accordingly. xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad // UP and DOWN direction accordingly. yaxis.compareTo(-1.0f) == 0 -> Dpad.UP yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN else -> directionPressed } } // If the input event is a KeyEvent, check its key code. (event as? KeyEvent)?.apply { // Use the key code to find the D-pad direction. directionPressed = when(event.keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN KeyEvent.KEYCODE_DPAD_CENTER -> Dpad.CENTER else -> directionPressed } } return directionPressed } companion object { internal const val UP = 0 internal const val LEFT = 1 internal const val RIGHT = 2 internal const val DOWN = 3 internal const val CENTER = 4 fun isDpadDevice(event: InputEvent): Boolean = // Check that input comes from a device with directional pads. event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD } }
Java
public class Dpad { final static int UP = 0; final static int LEFT = 1; final static int RIGHT = 2; final static int DOWN = 3; final static int CENTER = 4; int directionPressed = -1; // initialized to -1 public int getDirectionPressed(InputEvent event) { if (!isDpadDevice(event)) { return -1; } // If the input event is a MotionEvent, check its hat axis values. if (event instanceof MotionEvent) { // Use the hat axis value to find the D-pad direction MotionEvent motionEvent = (MotionEvent) event; float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X); float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y); // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad // LEFT and RIGHT direction accordingly. if (Float.compare(xaxis, -1.0f) == 0) { directionPressed = Dpad.LEFT; } else if (Float.compare(xaxis, 1.0f) == 0) { directionPressed = Dpad.RIGHT; } // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad // UP and DOWN direction accordingly. else if (Float.compare(yaxis, -1.0f) == 0) { directionPressed = Dpad.UP; } else if (Float.compare(yaxis, 1.0f) == 0) { directionPressed = Dpad.DOWN; } } // If the input event is a KeyEvent, check its key code. else if (event instanceof KeyEvent) { // Use the key code to find the D-pad direction. KeyEvent keyEvent = (KeyEvent) event; if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { directionPressed = Dpad.LEFT; } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) { directionPressed = Dpad.RIGHT; } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) { directionPressed = Dpad.UP; } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) { directionPressed = Dpad.DOWN; } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) { directionPressed = Dpad.CENTER; } } return directionPressed; } public static boolean isDpadDevice(InputEvent event) { // Check that input comes from a device with directional pads. if ((event.getSource() & InputDevice.SOURCE_DPAD) != InputDevice.SOURCE_DPAD) { return true; } else { return false; } } }
يمكنك استخدام فئة المساعدة هذه في لعبتك في أي مكان تريد معالجة
الإدخال من لوحة التوجيه (على سبيل المثال، في وظائف 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
للحصول على التفاصيل.
يمكنك استخدام المعلومات السابقة لعرض حركة كائن اللعبة بشكل أكثر دقة استنادًا إلى إدخال ذراع التحكّم. لاسترداد القيم الحالية والسابقة، اتصل بالرقم
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_
كي تبقى متوافقة مع جميع وحدات التحكّم الشائعة في الألعاب، ولكن يتم تفضيل الحدث الأكثر ملاءمةً لأسلوب لعبك إذا أبلغت وحدة التحكّم عن الاثنين معًا. في الإصدار 4.3 من نظام التشغيل Android (المستوى 18 من واجهة برمجة التطبيقات) والإصدارات الأحدث، يُرسِل جهاز التحكّم الذي يُنشئAXIS_LTRIGGER
قيمة متطابقة أيضًا لمحورAXIS_BRAKE
. وينطبق ذلك أيضًا علىAXIS_RTRIGGER
وAXIS_GAS
. يُبلغ نظام Android عن جميع ضغطات المشغلات التناظرية بقيمة تمت تسويتها من 0.0 (تم إصدارها) إلى 1.0 (تم الضغط عليها بالكامل). -
قد تختلف السلوكيات والدعم في البيئات التي تتم محاكاتها. قد يختلف سلوك المنصات المحاكية، مثل ألعاب Google Play، قليلاً استنادًا إلى إمكانات نظام التشغيل المضيف. على سبيل المثال، بعض أدوات التحكّم التي تُرسِل أحداث
AXIS_
وKEYCODE_BUTTON_
لا تُرسِل سوى أحداثAXIS_
، وقد لا تتوفّر أدوات التحكّم لبعض الأجهزة على الإطلاق.