التعامل مع إجراءات وحدة التحكّم

على مستوى النظام، يبلغ Android عن رموز أحداث الإدخال من وحدات تحكّم الألعاب على أنّها رموز مفاتيح Android وقيم المحاور. في لعبتك، يمكنك استلام هذه الرموز والقيم وتحويلها إلى إجراءات محددة داخل اللعبة.

عندما يتصل اللاعبون فعليًا بوحدة تحكم في الألعاب أو يقرنونها لاسلكيًا بأجهزتهم التي تعمل بنظام التشغيل Android، يكتشف النظام تلقائيًا وحدة التحكم كجهاز إدخال ويبدأ في الإبلاغ عن أحداث الإدخال. يمكن أن تتلقّى لعبتك أحداث الإدخال هذه من خلال تنفيذ طرق معاودة الاتصال التالية في Activity النشطة أو View المركزة (يجب تنفيذ عمليات معاودة الاتصال إما مع Activity أو View، ولكن ليس لكليهما):

والنهج المقترَح هو التقاط الأحداث من عنصر View المحدّد الذي يتفاعل معه المستخدم. افحص العناصر التالية التي توفّرها عمليات معاودة الاتصال للحصول على معلومات عن نوع حدث الإدخال الذي تم تلقّيه:

KeyEvent
يشير إلى عنصر يصف أحداث لوحة الاتجاهات (لوحة التحكّم) وزر لوحة التحكّم في الألعاب. تكون الأحداث الرئيسية مصحوبة برمز مفتاح يشير إلى الزر المحدّد الذي تم تشغيله، مثل DPAD_DOWN أو BUTTON_A. يمكنك الحصول على رمز المفتاح من خلال استدعاء الرقم getKeyCode() أو من عمليات استدعاء الحدث الرئيسي مثل onKeyDown().
MotionEvent
كائن يصف الإدخال من ذراع التحكّم وحركات الكتف. تكون أحداث الحركة مصحوبةً برمز إجراء ومجموعة من قيم المحاور. يحدد رمز الإجراء تغيير الحالة الذي حدث مثل تحريك ذراع التحكم. وتصف قيم المحور الموضع وخصائص الحركة الأخرى لعنصر تحكّم مادي محدّد، مثل AXIS_X أو AXIS_RTRIGGER. يمكنك الحصول على رمز الإجراء عن طريق استدعاء getAction() وقيمة المحور عن طريق استدعاء getAxisValue().

يركز هذا الدرس على كيفية التعامل مع الإدخال من أنواع عناصر التحكم المادية الأكثر شيوعًا (أزرار لوحة الألعاب ولوحات الاتجاهات وأذرع التحكّم) في شاشة اللعبة من خلال تنفيذ أساليب استدعاء View المذكورة أعلاه والمعالجة وكائنات KeyEvent وMotionEvent المذكورة أعلاه.

التأكّد من ربط وحدة تحكّم في الألعاب

عند الإبلاغ عن أحداث الإدخال، لا يميّز Android بين الأحداث الواردة من جهاز غير وحدة تحكُّم بالألعاب والأحداث التي تأتي من وحدة تحكُّم الألعاب. على سبيل المثال، يؤدي إجراء على الشاشة التي تعمل باللمس إلى إنشاء حدث AXIS_X الذي يمثّل الإحداثي "س" على سطح اللمس، في حين ينشئ ذراع التحكّم الحدث AXIS_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;
}

بالإضافة إلى ذلك، يمكنك التحقق من إمكانات الإدخال الفردية المتوافقة مع وحدة التحكم في الألعاب المتصلة. قد يكون هذا مفيدًا، على سبيل المثال، إذا كنت تريد أن تستخدم لعبتك إدخال البيانات من مجموعة عناصر التحكم المادية التي تفهمها فقط.

لاكتشاف ما إذا كان رمز مفتاح أو رمز محور يتوافق مع وحدة التحكم في الألعاب المرتبطة، يمكنك استخدام الأساليب التالية:

  • في الإصدار 4.4 من نظام التشغيل Android (المستوى 19 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك طلب ما إذا كان رمز المفتاح متوافقًا مع وحدة تحكّم الألعاب المرتبطة من خلال طلب التحديث hasKeys(int...).
  • في الإصدار Android 3.1 (المستوى 12 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك العثور على جميع المحاور المتاحة المتوافقة مع وحدة تحكّم الألعاب المتصلة من خلال طلب getMotionRanges() أولاً. بعد ذلك، استدعِ getAxis() في كل عنصر InputDevice.MotionRange تم عرضه، للحصول على رقم تعريف المحور.

معالجة الضغط على زر لوحة الألعاب

يوضح الشكل 1 كيفية تعيين Android للرموز الرئيسية وقيم المحاور لعناصر التحكم الفعلية في معظم وحدات التحكم في الألعاب.

الشكل 1. ملف شخصي لوحدة تحكم عامة في الألعاب

تشير وسائل الشرح في الشكل إلى ما يلي:

تشتمل رموز المفاتيح الشائعة التي يتم إنشاؤها من خلال الضغط على أزرار لوحة الألعاب على BUTTON_A وBUTTON_B وBUTTON_SELECT وBUTTON_START. وتشغّل بعض وحدات التحكّم في الألعاب أيضًا رمز المفتاح DPAD_CENTER عند الضغط على منتصف شريط لوحة التحكّم. يمكن للعبتك فحص رمز المفتاح من خلال طلب الرقم getKeyCode() أو من خلال طلبات استدعاء الأحداث الرئيسية مثل onKeyDown()، وإذا كان هذا الحدث يمثِّل حدثًا ذا صلة بلعبتك، يمكنك معالجته كإجراء لعبة. ويسرد الجدول 1 إجراءات الألعاب المقترحة لأزرار لوحة الألعاب الأكثر شيوعًا.

الجدول 1. إجراءات الألعاب المقترحة لأزرار لوحة الألعاب.

ألعاب الحركة رمز مفتاح الزر
يمكنك بدء اللعبة من القائمة الرئيسية أو إيقافها مؤقتًا أو استئنافها أثناء اللعب. BUTTON_START*
عرض القائمة BUTTON_SELECT* وKEYCODE_MENU*
مثل سلوك التنقّل للرجوع في نظام التشغيل Android الموضح في دليل تصميم التنقل. KEYCODE_BACK
الرجوع إلى عنصر سابق في قائمة BUTTON_B
تأكيد اختيارك أو تنفيذ إجراء أساسي في اللعبة BUTTON_A و DPAD_CENTER

* يجب ألا تعتمد لعبتك على أزرار "البدء" أو "اختيار" أو القائمة.

نصيحة: يمكنك توفير شاشة إعداد في لعبتك للسماح للمستخدمين بتخصيص تعيينات وحدات التحكم في الألعاب من أجل تنفيذ إجراءات اللعبة.

يعرض المقتطف التالي كيفية إلغاء 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.

إدخال لوحة الاتجاهات الخاصة بالمعالجة

تُعتبر لوحة الاتجاهات الرباعية وسيلة التحكّم الفعلية الشائعة في العديد من وحدات التحكّم في الألعاب. يعرض نظام Android تقارير D-pad لأعلى ولأسفل كأحداث AXIS_HAT_Y بنطاق يتراوح من -1.0 (لأعلى) إلى 1.0 (لأسفل)، بينما يضغط زر لوحة التحكّم LEFT أو RIGHT على AXIS_HAT_X أحداث بنطاق يتراوح من -1.0 (لليسار) إلى 1.0 (لليمين).

وبدلاً من ذلك، تبلّغ بعض وحدات التحكّم عن ضغطات لوحة التحكّم باستخدام رمز مفتاح. إذا كانت لعبتك تهتم بضغط لوحة التحكم، فينبغي أن تتعامل مع أحداث محور القبعة ورموز مفاتيح لوحة التحكم على أنها أحداث الإدخال نفسها، على النحو الموصى به في الجدول 2.

الجدول 2. إجراءات الألعاب التلقائية المقترَحة لرموز لوحة التحكّم وقيم محور القبعات

ألعاب الحركة رمز مفتاح لوحة التحكّم رمز محور Hat
نقل للأعلى 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.
            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;
         }
     }
}

يمكنك استخدام هذا الصف المساعِد في لعبتك في أي مكان تريد فيه معالجة إدخال لوحة التحكّم (على سبيل المثال، في 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. تأكد من التعامل مع كلا عصي وحدة التحكم في التعليمات البرمجية الخاصة بك.
  • التعامل مع الضغطات على مشغّل الكتف (مع توفير طرق إدخال بديلة) تحتوي بعض وحدات التحكم على مشغلات الكتف الأيمن والأيسر. في حال توفّر هذه المشغِّلات، يُبلغ Android عن الضغط على المشغِّل الأيسر كحدث AXIS_LTRIGGER وضغط المشغِّل الأيمن كحدث AXIS_RTRIGGER. في نظام التشغيل Android 4.3 (المستوى 18 من واجهة برمجة التطبيقات)، تُبلِغ أيضًا وحدة التحكّم التي تُنتج AXIS_LTRIGGER عن قيمة متطابقة للمحور AXIS_BRAKE. وينطبق الأمر نفسه على AXIS_RTRIGGER وAXIS_GAS. يبلغ نظام Android عن جميع ضغطات المشغل التناظري بقيمة تمت تسويتها من 0.0 (تم إصدارها) إلى 1.0 (تم الضغط عليها بالكامل). لا تحتوي جميع وحدات التحكم على مشغلات، لذا ننصحك بالسماح للّاعبين بتنفيذ هذه الإجراءات باستخدام أزرار أخرى.