على مستوى النظام، يُبلغ نظام 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...)
. - في Android 3.1 (المستوى 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; } }
ملاحظة: في الإصدار Android 4.2 (المستوى 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_
، وقد لا تتوفّر أدوات التحكّم لبعض الأجهزة على الإطلاق.