إتاحة استخدام عدة أذرع تحكُّم في الألعاب

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

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

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

عند توصيل وحدة تحكم في الألعاب بجهاز Android، يعيّن النظام رقم تعريف جهاز عددًا صحيحًا له. يمكنك الحصول على أرقام تعريف الأجهزة لوحدات التحكم في الألعاب المتصلة عن طريق الاتصال بـ InputDevice.getDeviceIds()، على النحو الموضح في قسم التحقق من اتصال وحدة التحكم في الألعاب. يمكنك بعد ذلك ربط كل رقم تعريف جهاز بلاعب في لعبتك، ومعالجة إجراءات اللعبة لكل لاعب على حدة.

ملاحظة: على الأجهزة التي تعمل بنظام التشغيل Android 4.1 (المستوى 16 من واجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك الحصول على أداة الوصف لجهاز الإدخال باستخدام getDescriptor() التي تعرض قيمة سلسلة ثابتة فريدة لجهاز الإدخال. على عكس رقم تعريف الجهاز، لن تتغير قيمة الواصف حتى إذا كان جهاز الإدخال غير متصل أو مُعاد توصيله أو تمت إعادة ضبطه.

يوضّح مقتطف الرمز أدناه كيفية استخدام SparseArray لربط الصورة الرمزية للاعب بوحدة تحكّم معيّنة. في هذا المثال، يُخزّن المتغيّر mShips مجموعة من عناصر Ship. يتم إنشاء صورة رمزية للاعب جديد داخل اللعبة عندما يوصل المستخدم وحدة تحكم جديدة، وتتم إزالتها عند إزالة وحدة التحكم المرتبطة بها.

تشكّل طريقتا معاودة الاتصال onInputDeviceAdded() وonInputDeviceRemoved() جزءًا من طبقة التجريد التي تم تقديمها في دعم وحدات التحكّم في إصدارات Android. ومن خلال تنفيذ استدعاءات الاستماع هذه، يمكن للعبتك التعرّف على رقم تعريف جهاز وحدة التحكّم في الألعاب عند إضافة وحدة تحكّم أو إزالتها. ويتوافق هذا الرصد مع Android 2.3 (المستوى 9 لواجهة برمجة التطبيقات) والإصدارات الأحدث.

Kotlin

private val ships = SparseArray<Ship>()

override fun onInputDeviceAdded(deviceId: Int) {
    getShipForID(deviceId)
}

override fun onInputDeviceRemoved(deviceId: Int) {
    removeShipForID(deviceId)
}

private fun getShipForID(shipID: Int): Ship {
    return ships.get(shipID) ?: Ship().also {
        ships.append(shipID, it)
    }
}

private fun removeShipForID(shipID: Int) {
    ships.remove(shipID)
}

Java

private final SparseArray<Ship> ships = new SparseArray<Ship>();

@Override
public void onInputDeviceAdded(int deviceId) {
    getShipForID(deviceId);
}

@Override
public void onInputDeviceRemoved(int deviceId) {
    removeShipForID(deviceId);
}

private Ship getShipForID(int shipID) {
    Ship currentShip = ships.get(shipID);
    if ( null == currentShip ) {
        currentShip = new Ship();
        ships.append(shipID, currentShip);
    }
    return currentShip;
}

private void removeShipForID(int shipID) {
    ships.remove(shipID);
}

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

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

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

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

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

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
        event.deviceId.takeIf { it != -1 }?.also { deviceId ->
            val currentShip: Ship = getShipForID(deviceId)
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            return true
        }
    }
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
        int deviceId = event.getDeviceId();
        if (deviceId != -1) {
            Ship currentShip = getShipForId(deviceId);
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            ...
            return true;
        }
    }
    return super.onKeyDown(keyCode, event);
}

ملاحظة: من أفضل الممارسات، عند إلغاء ربط وحدة التحكّم في اللعبة لدى أحد المستخدمين، عليك إيقاف اللعبة مؤقتًا والسؤال عمّا إذا كان المستخدم يريد إعادة الاتصال.