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

تتضمّن أدوات التحكّم نوعَين من الإجراءات:

  • KeyEvent يُستخدَم لأي زر بحالتَين ثنائيتَين هما "مفعّل" و "غير مفعّل"
  • يتم استخدام MotionEvent لأي محور يعرض نطاقًا من القيم. مثل القيم من -1 إلى 1 لعصا التحكم التناظرية أو من 0 إلى 1 لأزرار التشغيل التناظرية.

يمكنك قراءة هذه المدخلات من View الذي يتضمّن focus.

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 مباشرةً بدلاً من ذلك، إذا لزم الأمر.

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

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

وحدة تحكّم عامة في الألعاب تتضمّن مدخلات مصنّفة، مثل لوحة التحكّم الاتجاهية وعصا التحكم التناظرية والأزرار
الشكل 1. ملف شخصي لذراع تحكّم عام في الألعاب

يسرد الجدول أسماء الأحداث وأنواعها العادية لوحدات التحكّم في الألعاب. للاطّلاع على القائمة الكاملة للأحداث، راجِع الأنواع الشائعة. يرسل النظام أحداث 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);
      }
    }
    

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

لوحة الاتجاهات الرباعية، أو لوحة D، هي عنصر تحكّم مادي شائع في العديد من وحدات التحكّم في الألعاب. يُبلغ نظام التشغيل Android عن الضغطات على الاتجاهَين "أعلى" و"أسفل" في لوحة المفاتيح الاتجاهية على أنّها أحداث AXIS_HAT_Y، مع الإشارة إلى الاتجاه "أعلى" بالقيمة -1.0 والإشارة إلى الاتجاه "أسفل" بالقيمة 1.0. ويُبلغ عن ضغطات الزر الأيسر أو الأيمن على لوحة التحكّم الاتجاهية على شكل أحداث 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.

يمكنك استرداد القيم الحالية والسابقة باستخدام الطرق التالية:

يوضّح المقتطف التالي كيفية إلغاء 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
أجهزة التحكّم بأسلوب المفاتيح

تم تصميم وحدات التحكّم هذه عادةً لعائلة أجهزة Nintendo Switch*.

ترسل وحدات التحكّم هذه KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent تم تصنيف الزرَّين L2/R2 على وحدات التحكّم هذه على أنّهما ZL/ZR.

تؤدي وحدات التحكّم هذه أيضًا إلى تبديل وظائف الزرَّين A وB والزرَّين X وY، وبالتالي يصبح الزر KEYCODE_BUTTON_A هو الزر B والعكس صحيح.

أجهزة تحكّم بتصميم 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_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
زر التشغيل الأيمن AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

يجب الحرص على التأكّد من أنّ لعبتك يمكنها التعامل مع كل من KeyEvent وMotionEvent للحفاظ على التوافق مع أكبر عدد ممكن من وحدات التحكّم، والتأكّد من إزالة الأحداث المكرّرة.

وحدات التحكّم المتوافقة

عند إجراء الاختبار، ننصحك بالتأكّد من أنّ لعبتك تعمل مع وحدة تحكّم واحدة في كل فئة.

  • أسلوب Xbox
  • تصميم Nintendo Switch
  • أسلوب PlayStation

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