بدء استخدام حزمة تطوير البرامج (SDK) للإدخال

يوضّح هذا المستند كيفية إعداد حزمة Input SDK وعرضها في الألعاب المتوافقة مع "ألعاب Google Play على الكمبيوتر". وتشمل المهام إضافة حزمة SDK إلى لعبتك وإنشاء خريطة إدخال تحتوي على عمليات ربط بين إجراءات اللعبة وإدخالات المستخدم.

قبل البدء

قبل إضافة حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال إلى لعبتك، يجب أن تتوافق مع لوحة المفاتيح والماوس باستخدام نظام الإدخال في محرك الألعاب.

توفّر حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال معلومات إلى برنامج "ألعاب Google Play على الكمبيوتر" حول عناصر التحكّم التي تستخدمها لعبتك، كي يتم عرضها للمستخدم. ويمكنه أيضًا السماح للمستخدمين بإعادة تعيين مفاتيح لوحة المفاتيح.

كل عنصر تحكّم هو InputAction (مثل "J" لـ "القفز")، ويمكنك تنظيم InputActions في InputGroups. قد يمثّل InputGroup وضعًا مختلفًا في لعبتك، مثل "القيادة" أو "المشي" أو "القائمة الرئيسية". يمكنك أيضًا استخدام InputContexts للإشارة إلى المجموعات النشطة في نقاط مختلفة من اللعبة.

يمكنك تفعيل إعادة ربط أزرار لوحة المفاتيح ليتم التعامل معها تلقائيًا، ولكن إذا كنت تفضّل توفير واجهة خاصة لإعادة ربط عناصر التحكّم، يمكنك إيقاف إعادة ربط عناصر التحكّم في حزمة Input SDK.

يوضّح مخطط التسلسل التالي طريقة عمل واجهة برمجة التطبيقات لحزمة تطوير البرامج (SDK) الخاصة بأداة الإدخال:

مخطّط تسلسلي لتنفيذ لعبة تستدعي واجهة برمجة التطبيقات Input SDK API وتفاعلها مع جهاز Android

عندما تستخدم لعبتك حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال، ستظهر عناصر التحكّم في التراكب الخاص ببرنامج "ألعاب Google Play على الكمبيوتر".

التراكب في "ألعاب Google Play على الكمبيوتر"

تعرض طبقة التراكب في برنامج "ألعاب Google Play على الكمبيوتر" ("طبقة التراكب") عناصر التحكّم التي تحدّدها لعبتك. يمكن للمستخدمين الوصول إلى التراكب في أي وقت من خلال الضغط على Shift + Tab.

تراكب "ألعاب Google Play على الكمبيوتر"

أفضل الممارسات لتصميم اختصارات لوحة المفاتيح

عند تصميم اختصارات لوحة المفاتيح، يُرجى مراعاة أفضل الممارسات التالية:

  • قسِّم InputActions إلى InputGroups ذات صلة منطقية لتحسين التنقّل وسهولة العثور على عناصر التحكّم أثناء اللعب.
  • يمكنك ربط كل InputGroup بـ InputContext واحد كحدّ أقصى. يؤدي استخدام قيمة دقيقة InputMap إلى تحسين تجربة التنقّل بين عناصر التحكّم في التراكب.
  • أنشِئ InputContext لكل نوع مشهد مختلف في لعبتك. يمكنك عادةً استخدام InputContext واحد لجميع المشاهد التي تشبه القائمة. استخدِم InputContexts لأي ألعاب مصغّرة في لعبتك أو لعناصر تحكّم بديلة لمشهد واحد.
  • إذا كان الإجراءان مصمَّمَين لاستخدام المفتاح نفسه ضمن InputContext نفسه، استخدِم سلسلة التصنيف، مثل "التفاعل / الإطلاق".
  • إذا تم تصميم مفتاحَين للربط بالإجراء InputAction نفسه، استخدِم InputActions مختلفَين ينفّذان الإجراء نفسه في لعبتك. يمكنك استخدام سلسلة التصنيف نفسها لكلتا السمتَين InputActions، ولكن يجب أن يكون رقم التعريف مختلفًا.
  • إذا تم تطبيق مفتاح تعديل على مجموعة من المفاتيح، ننصحك باستخدام InputAction واحد مع مفتاح التعديل بدلاً من استخدام عدة InputActions تجمع مفتاح التعديل (مثال: استخدام Shift وW وA وS وD بدلاً من Shift + W وShift + A وShift + S وShift + D).
  • يتم إيقاف إعادة تعيين الإدخال تلقائيًا عندما يكتب المستخدم في حقول النص. اتّبِع أفضل الممارسات لتنفيذ حقول النص في Android لضمان قدرة Android على رصد حقول النص في لعبتك ومنع المفاتيح التي تمت إعادة تعيينها من التداخل معها. إذا كانت لعبتك تستخدم حقول نصية غير تقليدية، يمكنك استخدام setInputContext() مع InputContext تحتوي على قائمة فارغة من InputGroups لإيقاف إعادة الربط يدويًا.
  • إذا كانت لعبتك تتيح إعادة تعيين المفاتيح، ننصحك بتعديل عمليات ربط المفاتيح، لأنّها عملية حساسة يمكن أن تتعارض مع الإصدارات المحفوظة من قِبل المستخدم. تجنَّب تغيير أرقام تعريف عناصر التحكّم الحالية متى أمكن ذلك.

ميزة إعادة الربط

يتيح برنامج "ألعاب Google Play على الكمبيوتر" إعادة ربط عناصر التحكّم في لوحة المفاتيح استنادًا إلى عمليات ربط المفاتيح التي توفّرها لعبتك باستخدام حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال. هذه الخطوة اختيارية ويمكن إيقافها نهائيًا. على سبيل المثال، قد ترغب في توفير واجهة خاصة بك لإعادة ربط مفاتيح لوحة المفاتيح. لإيقاف إعادة الربط في لعبتك، ما عليك سوى تحديد خيار إعادة الربط على أنّه غير مفعّل في InputMap (راجِع إنشاء InputMap لمزيد من المعلومات).

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

محاولة إعادة تعيين المفتاح

يخزِّن برنامج "ألعاب Google Play على الكمبيوتر" عناصر التحكّم التي تم إعادة تعيينها محليًا لكل مستخدم، ما يتيح استمرار عناصر التحكّم في جميع جلسات الألعاب. يتم تخزين هذه المعلومات على القرص فقط لمنصة الكمبيوتر الشخصي ولا تؤثر في تجربة الأجهزة الجوّالة. يتم حذف بيانات التحكّم عندما يلغي المستخدم تثبيت تطبيق "ألعاب Google Play على الكمبيوتر" أو يعيد تثبيته. ولا يتم الاحتفاظ بهذه البيانات على أجهزة كمبيوتر متعددة.

لإتاحة ميزة إعادة تعيين المفاتيح في لعبتك، تجنَّب القيود التالية:

قيود إعادة الربط

يمكن إيقاف ميزات إعادة تعيين المفاتيح في لعبتك إذا كانت عمليات ربط المفاتيح تتضمّن أيًا من الحالات التالية:

  • مفاتيح InputActions متعددة لا تتألف من مفتاح تعديل + مفتاح غير معدِّل. على سبيل المثال، Shift + A هو اختصار صالح، ولكن A + B أو Ctrl + Alt أو Shift + A + Tab ليس اختصارًا صالحًا.
  • تحتوي السمة InputMap على InputActions أو InputGroups أو InputContexts مع معرّفات فريدة مكرّرة.

قيود إعادة الربط

عند تصميم مفاتيحك الرئيسية لإعادة تعيينها، يجب مراعاة القيود التالية:

  • لا يمكن إعادة تعيين المفاتيح إلى مجموعات مفاتيح. على سبيل المثال، لا يمكن للمستخدمين إعادة تعيين Shift + A إلى Ctrl + B أو A إلى Shift + A.
  • لا يمكن إعادة تعيين InputActions باستخدام أزرار الماوس. على سبيل المثال، لا يمكن إعادة تعيين Shift + النقر بزر الماوس الأيمن.

اختبار إعادة تعيين مفاتيح لوحة المفاتيح في محاكي برنامج "ألعاب Google Play على الكمبيوتر"

يمكنك تفعيل ميزة إعادة تعيين المفاتيح في "محاكي ألعاب Google Play على الكمبيوتر" في أي وقت من خلال تنفيذ أمر adb التالي:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

تغيير الطبقة الخارجية كما في الصورة التالية:

العناصر المركّبة مع تفعيل إعادة تعيين المفاتيح

إضافة حزمة تطوير البرامج (SDK)

ثبِّت حزمة تطوير البرامج (SDK) الخاصة بأداة Input وفقًا لنظام التطوير الأساسي الذي تستخدمه.

Java وKotlin

يمكنك الحصول على حزمة تطوير البرامج (SDK) الخاصة بأداة Input API للغة Java أو Kotlin من خلال إضافة مصدر اعتمادية إلى ملف build.gradle على مستوى الوحدة:

dependencies {
  implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
  ...
}

Unity

‫Input SDK هي حزمة Unity عادية تتضمّن العديد من العناصر التابعة.

يجب تثبيت الحزمة مع جميع العناصر التابعة. هناك عدة طرق لتثبيت الحِزم.

تثبيت .unitypackage

نزِّل ملف Input SDK unitypackage مع جميع التبعيات. يمكنك تثبيت .unitypackage من خلال اختيار مواد العرض > استيراد حزمة > حزمة مخصّصة والعثور على الملف الذي نزّلته.

التثبيت باستخدام UPM

يمكنك بدلاً من ذلك تثبيت الحزمة باستخدام مدير حِزم Unity من خلال تنزيل .tgz وتثبيت التبعيات:

التثبيت باستخدام OpenUPM

يمكنك تثبيت الحزمة باستخدام OpenUPM.

$ openupm add com.google.android.libraries.play.games.inputmapping

ألعاب تجريبية

للاطّلاع على أمثلة حول كيفية الدمج مع Input SDK، راجِع AGDK Tunnel لألعاب Kotlin أو Java وTrivial Kart لألعاب Unity.

إنشاء ربط المفاتيح

سجِّل ربط المفاتيح من خلال إنشاء InputMap وعرضه مع InputMappingProvider. يوضّح المثال التالي InputMappingProvider:

Kotlin

class InputSDKProvider : InputMappingProvider {
  override fun onProvideInputMap(): InputMap {
    TODO("Not yet implemented")
  }
}

Java

public class InputSDKProvider implements InputMappingProvider {
    private static final String INPUTMAP_VERSION = "1.0.0";

    @Override
    @NonNull
    public InputMap onProvideInputMap() {
        // TODO: return an InputMap
    }
}

#C

#if PLAY_GAMES_PC
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    public override InputMap OnProvideInputMap()
    {
        // TODO: return an InputMap
    }
}
#endif

تحديد إجراءات الإدخال

يتم استخدام الفئة InputAction لربط مفتاح أو مجموعة مفاتيح بإجراء في اللعبة. يجب أن تتضمّن InputActions معرّفات فريدة في جميع InputActions.

إذا كنت تتيح إعادة ربط الأزرار، يمكنك تحديد الأزرار التي يمكن إعادة ربطها.InputActions إذا كانت لعبتك لا تتيح إعادة ربط الأزرار، عليك إيقاف خيار إعادة الربط لجميع InputActions، ولكن حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال ذكية بما يكفي لإيقاف إعادة الربط إذا لم تكن لعبتك تتيحها في InputMap.

يربط هذا المثال مفتاح المسافة بالإجراء قيادة.

Kotlin

companion object {
  private val driveInputAction = InputAction.create(
    "Drive",
    InputActionsIds.DRIVE.ordinal.toLong(),
    InputControls.create(listOf(KeyEvent.KEYCODE_SPACE), emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Java

private static final InputAction driveInputAction = InputAction.create(
    "Drive",
    InputEventIds.DRIVE.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()),
    InputEnums.REMAP_OPTION_ENABLED
);

#C

private static readonly InputAction driveInputAction = InputAction.Create(
    "Drive",
    (long)InputEventIds.DRIVE,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

يتم عرض InputAction بمفتاح واحد في الصورة المتراكبة.

يمكن أن تمثّل الإجراءات أيضًا إدخالات الماوس. يضبط هذا المثال النقر بزر الماوس الأيسر على الإجراء نقل:

Kotlin

companion object {
  private val mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal.toLong(),
    InputControls.create(emptyList(), listOf(InputControls.MOUSE_LEFT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Java

private static final InputAction mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal(),
    InputControls.create(
            Collections.emptyList(),
            Collections.singletonList(InputControls.MOUSE_LEFT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

#C

private static readonly InputAction mouseInputAction = InputAction.Create(
    "Move",
    (long)InputEventIds.MOUSE_MOVEMENT,
    InputControls.Create(
        new ArrayList<Integer>(),
        new[] { new Integer((int)PlayMouseAction.MouseLeftClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

يتم عرض MouseInputAction في سطح الصفحة.

يتم تحديد مجموعات المفاتيح من خلال تمرير رموز مفاتيح متعددة إلى InputAction. في هذا المثال، يتم ربط المسافة + Shift بالإجراء Turbo، الذي يعمل حتى عندما يتم ربط المسافة بالإجراء Drive.

Kotlin

companion object {
  private val turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
      emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Java

private static final InputAction turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal(),
    InputControls.create(
            Arrays.asList(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()
    ),
    InputEnums.REMAP_OPTION_ENABLED
);

#C

private static readonly InputAction turboInputAction = InputAction.Create(
    "Turbo",
    (long)InputEventIds.TURBO,
    InputControls.Create(
        new[]
        {
            new Integer(AndroidKeyCode.KEYCODE_SHIFT_LEFT),
            new Integer(AndroidKeyCode.KEYCODE_SPACE)
        }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

يتم عرض Multi-key InputAction في التراكب.

تتيح لك حزمة تطوير البرامج (SDK) الخاصة بإدخال البيانات دمج أزرار الماوس والمفاتيح معًا لتنفيذ إجراء واحد. يشير هذا المثال إلى أنّ الضغط على Shift والنقر بزر الماوس الأيمن معًا يؤدي إلى إضافة نقطة طريق في هذه اللعبة النموذجية:

Kotlin

companion object {
  private val addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KeyEvent.KEYCODE_TAB),
      listOf(InputControls.MOUSE_RIGHT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Java

private static final InputAction addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_TAB),
            Collections.singletonList(InputControls.MOUSE_RIGHT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

#C

private static readonly InputAction addWaypointInputAction = InputAction.Create(
    "Add waypoint",
    (long)InputEventIds.ADD_WAYPOINT,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new[] { new Integer((int)PlayMouseAction.MouseRightClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

مجموعة المفتاح + إجراء الماوس المعروضة في التراكب

يحتوي InputAction على الحقول التالية:

  • ActionLabel: السلسلة المعروضة في واجهة المستخدم لتمثيل هذا الإجراء. لا تتم عملية الترجمة تلقائيًا، لذا عليك تنفيذها مسبقًا.
  • InputControls: تحدّد عناصر التحكّم في الإدخال التي يستخدمها هذا الإجراء. يتم ربط عناصر التحكّم برموز رسومية متسقة في التراكب.
  • InputActionId: عنصر InputIdentifier يخزّن رقم التعريف والإصدار الخاصين بـ InputAction (راجِع معرّفات مفاتيح التتبُّع لمزيد من المعلومات).
  • InputRemappingOption: إحدى القيمتين InputEnums.REMAP_OPTION_ENABLED أو InputEnums.REMAP_OPTION_DISABLED تحدِّد هذه السمة ما إذا كان الإجراء مفعّلاً لإعادة التعيين. إذا كانت لعبتك لا تتيح إعادة الربط، يمكنك تخطّي هذا الحقل أو ضبطه على "غير مفعّل".
  • RemappedInputControls: كائن InputControls للقراءة فقط يُستخدَم لقراءة مجموعة المفاتيح التي أعاد المستخدم تعيينها عند وقوع أحداث إعادة التعيين (يُستخدَم لتلقّي إشعارات بشأن أحداث إعادة التعيين).

يمثّل InputControls المدخلات المرتبطة بأحد الإجراءات ويتضمّن الحقول التالية:

  • AndroidKeycodes: هي قائمة بأعداد صحيحة تمثّل إدخالات لوحة المفاتيح المرتبطة بأحد الإجراءات. يتم تحديدها في فئة KeyEvent أو فئة AndroidKeycode في Unity.
  • MouseActions: هي قائمة بقيم MouseAction تمثّل إدخالات الماوس المرتبطة بهذا الإجراء.

تحديد مجموعات الإدخال

يتم تجميع InputActions مع الإجراءات ذات الصلة منطقيًا باستخدام InputGroups لتحسين التنقّل وإمكانية العثور على عناصر التحكّم في التراكب. يجب أن يكون كل معرّف InputGroup فريدًا في جميع InputGroups داخل لعبتك.

من خلال تنظيم إجراءات الإدخال في مجموعات، يمكنك تسهيل عثور اللاعب على ربط المفاتيح الصحيح للسياق الحالي.

إذا كنت تتيح إعادة ربط الأزرار، يمكنك تحديد الأزرار التي يمكن إعادة ربطها.InputGroups إذا كانت لعبتك لا تتيح إعادة ربط الأزرار، عليك إيقاف خيار إعادة الربط لجميع InputGroups، ولكن حزمة تطوير البرامج (SDK) الخاصة بأدوات الإدخال ذكية بما يكفي لإيقاف إعادة الربط إذا لم تكن لعبتك تتيحها في InputMap.

Kotlin

companion object {
  private val menuInputGroup = InputGroup.create(
    "Menu keys",
    listOf(
      navigateUpInputAction,
      navigateLeftInputAction,
      navigateDownInputAction,
      navigateRightInputAction,
      openMenuInputAction,
      returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal.toLong(),
    InputEnums.REMAP_OPTION_ENABLED
  )
}

Java

private static final InputGroup menuInputGroup = InputGroup.create(
    "Menu keys",
    Arrays.asList(
           navigateUpInputAction,
           navigateLeftInputAction,
           navigateDownInputAction,
           navigateRightInputAction,
           openMenuInputAction,
           returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal(),
    REMAP_OPTION_ENABLED
);

#C

private static readonly InputGroup menuInputGroup = InputGroup.Create(
    "Menu keys",
    new[]
    {
        navigateUpInputAction,
        navigateLeftInputAction,
        navigateDownInputAction,
        navigateRightInputAction,
        openMenuInputAction,
        returnMenuInputAction,
    }.ToJavaList(),
    (long)InputGroupsIds.MENU_ACTION_KEYS,
    InputEnums.REMAP_OPTION_ENABLED
);

يعرض المثال التالي مجموعتَي الإدخال عناصر التحكّم على الطريق وعناصر التحكّم في القائمة في التراكب:

تراكب يعرض InputMap يحتوي على مجموعات إدخال عناصر التحكّم في الطريق وعناصر التحكّم في القائمة

يتضمّن InputGroup الحقول التالية:

  • GroupLabel: هي سلسلة سيتم عرضها في التراكب ويمكن استخدامها لتجميع مجموعة من الإجراءات بشكل منطقي. لا يتم تلقائيًا توفير نسخة مترجمة من هذه السلسلة.
  • InputActions: قائمة بعناصر InputAction تحدّدها في الخطوة السابقة. يتم عرض كل هذه الإجراءات بشكل مرئي ضمن عنوان المجموعة.
  • InputGroupId: عنصر InputIdentifier يخزّن رقم التعريف وإصدار InputGroup. يمكنك الاطّلاع على معرّفات مفاتيح التتبّع للحصول على مزيد من المعلومات.
  • InputRemappingOption: إحدى القيمتين InputEnums.REMAP_OPTION_ENABLED أو InputEnums.REMAP_OPTION_DISABLED في حال إيقاف هذا الخيار، سيتم إيقاف إعادة الربط لجميع عناصر InputAction التابعة لهذه المجموعة، حتى إذا كانت تحدّد خيار إعادة الربط على أنّه مفعّل. في حال تفعيل هذا الخيار، يمكن إعادة ربط جميع الإجراءات التابعة لهذه المجموعة ما لم يتم تحديد أنّها غير قابلة للربط من خلال الإجراءات الفردية.

تحديد سياقات الإدخال

تسمح السمة InputContexts للعبتك باستخدام مجموعة مختلفة من عناصر التحكّم في لوحة المفاتيح لمشاهد مختلفة في لعبتك. مثلاً:

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

عند استخدام InputContexts، يعرض التراكب أولاً مجموعات السياق المستخدَمة. لتفعيل هذا السلوك، استدعِ الدالة setInputContext() لضبط السياق كلما انتقلت لعبتك إلى مشهد مختلف. توضّح الصورة التالية هذا السلوك: في مشهد "القيادة"، تظهر إجراءات عناصر التحكّم على الطريق في أعلى التراكب. عند فتح قائمة "المتجر"، تظهر إجراءات "عناصر التحكّم في القائمة" في أعلى التراكب.

مجموعات ترتيب InputContexts في التراكب

يتم عرض هذه التحديثات على الشاشة من خلال ضبط قيمة InputContext مختلفة في نقاط مختلفة من لعبتك. ولإجراء ذلك:

  1. تجميع InputActions مع الإجراءات ذات الصلة منطقيًا باستخدام InputGroups
  2. يمكنك تعيين هذه InputGroups إلى InputContext للأجزاء المختلفة من لعبتك

لا يمكن أن تتضمّن InputGroups التي تنتمي إلى InputContext تعارضًا في InputActions حيث يتم استخدام المفتاح نفسه. من الممارسات الجيدة ربط كل InputGroup بـ InputContext واحد.

يوضّح الرمز النموذجي التالي منطق InputContext:

Kotlin

companion object {
  val menuSceneInputContext = InputContext.create(
    "Menu",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.MENU_SCENE.ordinal.toLong()),
    listOf(basicMenuNavigationInputGroup, menuActionsInputGroup))

  val gameSceneInputContext = InputContext.create(
    "Game",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.GAME_SCENE.ordinal.toLong()),
    listOf(
      movementInputGroup,
      mouseActionsInputGroup,
      emojisInputGroup,
      gameActionsInputGroup))
}

Java

public static final InputContext menuSceneInputContext = InputContext.create(
        "Menu",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.MENU_SCENE.ordinal()),
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionsInputGroup
        )
);

public static final InputContext gameSceneInputContext = InputContext.create(
        "Game",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.GAME_SCENE.ordinal()),
        Arrays.asList(
                movementInputGroup,
                mouseActionsInputGroup,
                emojisInputGroup,
                gameActionsInputGroup
        )
);

#C

public static readonly InputContext menuSceneInputContext = InputContext.Create(
    "Menu",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.MENU_SCENE),
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionsInputGroup
    }.ToJavaList()
);

public static readonly InputContext gameSceneInputContext = InputContext.Create(
    "Game",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.GAME_SCENE),
    new[]
    {
        movementInputGroup,
        mouseActionsInputGroup,
        emojisInputGroup,
        gameActionsInputGroup
    }.ToJavaList()
);

يتضمّن InputContext الحقول التالية:

  • LocalizedContextLabel: سلسلة تصف المجموعات التي تنتمي إلى السياق.
  • InputContextId: عنصر InputIdentifier يخزّن رقم التعريف والإصدار الخاصين بـ InputContext (راجِع معرّفات مفاتيح التتبُّع لمزيد من المعلومات).
  • ActiveGroups: قائمة InputGroups سيتم استخدامها وعرضها في أعلى التراكب عندما يكون هذا السياق نشطًا.

إنشاء خريطة إدخال

InputMap هي مجموعة من جميع عناصر InputGroup المتاحة في إحدى الألعاب، وبالتالي جميع عناصر InputAction التي يمكن للاعب توقّع تنفيذها.

عند إعداد تقارير عن ربط المفاتيح، عليك إنشاء InputMap يتضمّن جميع InputGroups المستخدَمة في لعبتك.

إذا كانت لعبتك لا تتيح إعادة تعيين الأزرار، اضبط خيار إعادة تعيين الأزرار على "غير مفعّل"، واترك المفاتيح المحجوزة فارغة.

ينشئ المثال التالي InputMap مستخدَمًا للإبلاغ عن مجموعة من InputGroups.

Kotlin

companion object {
  val gameInputMap = InputMap.create(
    listOf(
      basicMenuNavigationInputGroup,
      menuActionKeysInputGroup,
      movementInputGroup,
      mouseMovementInputGroup,
      pauseMenuInputGroup),
    MouseSettings.create(true, false),
    InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID.toLong()),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    listof(InputControls.create(listOf(KeyEvent.KEYCODE_ESCAPE), emptyList()))
  )
}

Java

public static final InputMap gameInputMap = InputMap.create(
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionKeysInputGroup,
                movementInputGroup,
                mouseMovementInputGroup,
                pauseMenuInputGroup),
        MouseSettings.create(true, false),
        InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID),
        REMAP_OPTION_ENABLED,
        // Use ESCAPE as reserved remapping key
        Arrays.asList(
                InputControls.create(
                        Collections.singletonList(KeyEvent.KEYCODE_ESCAPE),
                        Collections.emptyList()
                )
        )
);

#C

public static readonly InputMap gameInputMap = InputMap.Create(
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionKeysInputGroup,
        movementInputGroup,
        mouseMovementInputGroup,
        pauseMenuInputGroup,
    }.ToJavaList(),
    MouseSettings.Create(true, false),
    InputIdentifier.Create(INPUT_MAP_VERSION, INPUT_MAP_ID),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    new[]
    {
        InputControls.Create(
            New[] {
            new Integer(AndroidKeyCode.KEYCODE_ESCAPE)
        }.ToJavaList(),
        new ArrayList<Integer>())
    }.ToJavaList()
);

يتضمّن InputMap الحقول التالية:

  • InputGroups: هي InputGroups التي أبلغت عنها لعبتك. يتم عرض المجموعات بالترتيب في التراكب، ما لم يتم تحديد المجموعات الحالية قيد الاستخدام من خلال استدعاء setInputContext().
  • MouseSettings: يشير العنصر MouseSettings إلى أنّه يمكن تعديل حساسية الماوس وأنّ الماوس معكوس على المحور y.
  • InputMapId: عنصر InputIdentifier يخزّن رقم التعريف والإصدار الخاصين بـ InputMap (راجِع أرقام تعريف مفاتيح التتبّع لمزيد من المعلومات).
  • InputRemappingOption: إحدى القيمتين InputEnums.REMAP_OPTION_ENABLED أو InputEnums.REMAP_OPTION_DISABLED تحدِّد هذه السمة ما إذا كانت ميزة إعادة الربط مفعَّلة.
  • ReservedControls: قائمة InputControls التي لن يُسمح للمستخدمين بإعادة ربطها.

تتبُّع أرقام التعريف الرئيسية

تحتوي عناصر InputAction وInputGroup وInputContext وInputMap على عنصر InputIdentifier يخزّن رقم تعريف فريدًا ومعرّف إصدار على شكل سلسلة. تتبُّع إصدار السلسلة من عناصرك هو إجراء اختياري، ولكن ننصح به لتتبُّع إصدارات InputMap. إذا لم يتم توفير إصدار السلسلة، ستكون السلسلة فارغة. يجب توفير إصدار سلسلة لكائنات InputMap.

يعيّن المثال التالي إصدار سلسلة إلى InputActions أو InputGroups:

Kotlin

class InputSDKProviderKotlin : InputMappingProvider {
  companion object {
    const val INPUTMAP_VERSION = "1.0.0"
    private val enterMenuInputAction = InputAction.create(
      "Enter menu",
      InputControls.create(listOf(KeyEvent.KEYCODE_ENTER), emptyList()),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED
    )

    private val movementInputGroup  = InputGroup.create(
      "Basic movement",
      listOf(
        moveUpInputAction,
        moveLeftInputAction,
        moveDownInputAction,
        mouseGameInputAction),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED)
  }
}

Java

public class InputSDKProvider implements InputMappingProvider {
    public static final String INPUTMAP_VERSION = "1.0.0";

    private static final InputAction enterMenuInputAction = InputAction.create(
            "Enter menu",
            InputControls.create(
                    Collections.singletonList(KeyEvent.KEYCODE_ENTER),
                    Collections.emptyList()),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );

    private static final InputGroup movementInputGroup = InputGroup.create(
            "Basic movement",
            Arrays.asList(
                    moveUpInputAction,
                    moveLeftInputAction,
                    moveDownInputAction,
                    moveRightInputAction,
                    mouseGameInputAction
            ),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );
}

#C

#if PLAY_GAMES_PC

using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKMappingProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    private static readonly InputAction enterMenuInputAction =
        InputAction.Create(
            "Enter menu",
            InputControls.Create(
                new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE)}.ToJavaList(),
                new ArrayList<Integer>()),
            InputIdentifier.Create(
                INPUT_MAP_VERSION,
                (long)InputEventIds.ENTER_MENU),
            InputEnums.REMAP_OPTION_ENABLED
        );

    private static readonly InputGroup movementInputGroup = InputGroup.Create(
        "Basic movement",
        new[]
        {
            moveUpInputAction,
            moveLeftInputAction,
            moveDownInputAction,
            moveRightInputAction,
            mouseGameInputAction
        }.ToJavaList(),
        InputIdentifier.Create(
            INPUT_MAP_VERSION,
            (long)InputGroupsIds.BASIC_MOVEMENT),
        InputEnums.REMAP_OPTION_ENABLED
    );
}
#endif

يجب أن تكون أرقام تعريف عناصر InputAction فريدة في جميع InputActions ضمن InputMap. وبالمثل، يجب أن تكون معرّفات عناصر InputGroup فريدة في جميع InputGroups ضمن InputMap. يوضّح المثال التالي كيفية استخدام enum لتتبُّع المعرّفات الفريدة للعناصر:

Kotlin

enum class InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

enum class InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

enum class InputContextIds {
    MENU_SCENE, // Basic menu navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

const val INPUT_MAP_ID = 0

Java

public enum InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds {
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static final long INPUT_MAP_ID = 0;

#C

public enum InputActionsIds
{
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds
{
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds
{
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static readonly long INPUT_MAP_ID = 0;

يتضمّن InputIdentifier الحقول التالية:

  • UniqueId: رقم تعريف فريد يتم ضبطه لتحديد مجموعة معيّنة من بيانات الإدخال بشكل فريد.
  • VersionString: سلسلة إصدار قابلة للقراءة من قِبل الإنسان تم ضبطها لتحديد إصدار من بيانات الإدخال بين إصدارَين من تغييرات بيانات الإدخال.

تلقّي إشعارات بشأن أحداث إعادة الربط (اختياري)

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

تعرض الصورة التالية مثالاً على هذا السلوك، حيث يتم تعديل عناصر واجهة المستخدم في اللعبة بعد إعادة ربط المفاتيح G وP وS بالمفاتيح J وX وT على التوالي، وذلك لعرض المفاتيح التي ضبطها المستخدم.

واجهة مستخدم تتفاعل مع أحداث إعادة الربط باستخدام معاودة الاتصال InputRemappingListener.

يتم تحقيق هذه الوظيفة من خلال تسجيل InputRemappingListener لرد الاتصال. لتنفيذ هذه الميزة، ابدأ بتسجيل مثيل InputRemappingListener:

Kotlin

class InputSDKRemappingListener : InputRemappingListener {
  override fun onInputMapChanged(inputMap: InputMap) {
    Log.i(TAG, "Received update on input map changed.")
    if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
      return
    }
    for (inputGroup in inputMap.inputGroups()) {
      if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
        continue
      }
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) {
          // Found InputAction remapped by user
          processRemappedAction(inputAction)
        }
      }
    }
  }

  private fun processRemappedAction(remappedInputAction: InputAction) {
    // Get remapped action info
    val remappedControls = remappedInputAction.remappedInputControls()
    val remappedKeyCodes = remappedControls.keycodes()
    val mouseActions = remappedControls.mouseActions()
    val version = remappedInputAction.inputActionId().versionString()
    val remappedActionId = remappedInputAction.inputActionId().uniqueId()
    val currentInputAction: Optional<InputAction>
    currentInputAction = if (version == null || version.isEmpty()
      || version == InputSDKProvider.INPUTMAP_VERSION
    ) {
      getCurrentVersionInputAction(remappedActionId)
    } else {
      Log.i(TAG,
            "Detected version of user-saved input action defers from current version")
      getCurrentVersionInputActionFromPreviousVersion(
        remappedActionId, version)
    }
    if (!currentInputAction.isPresent) {
      Log.e(TAG, String.format(
        "can't find remapped input action with id %d and version %s",
        remappedActionId, if (version == null || version.isEmpty()) "UNKNOWN" else version))
      return
    }
    val originalControls = currentInputAction.get().inputControls()
    val originalKeyCodes = originalControls.keycodes()
    Log.i(TAG, String.format(
      "Found input action with id %d remapped from key %s to key %s",
      remappedActionId,
      keyCodesToString(originalKeyCodes),
      keyCodesToString(remappedKeyCodes)))

    // TODO: make display changes to match controls used by the user
  }

  private fun getCurrentVersionInputAction(inputActionId: Long): Optional<InputAction> {
    for (inputGroup in InputSDKProvider.gameInputMap.inputGroups()) {
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputActionId().uniqueId() == inputActionId) {
          return Optional.of(inputAction)
        }
      }
    }
    return Optional.empty()
  }

  private fun getCurrentVersionInputActionFromPreviousVersion(
    inputActionId: Long, previousVersion: String
  ): Optional<InputAction7gt; {
    // TODO: add logic to this method considering the diff between the current and previous
    //  InputMap.
    return Optional.empty()
  }

  private fun keyCodesToString(keyCodes: List<Int>): String {
    val builder = StringBuilder()
    for (keyCode in keyCodes) {
      if (!builder.toString().isEmpty()) {
        builder.append(" + ")
      }
      builder.append(keyCode)
    }
    return String.format("(%s)", builder)
  }

  companion object {
    private const val TAG = "InputSDKRemappingListener"
  }
}

Java

public class InputSDKRemappingListener implements InputRemappingListener {

    private static final String TAG = "InputSDKRemappingListener";

    @Override
    public void onInputMapChanged(InputMap inputMap) {
        Log.i(TAG, "Received update on input map changed.");
        if (inputMap.inputRemappingOption() ==
                InputEnums.REMAP_OPTION_DISABLED) {
            return;
        }
        for (InputGroup inputGroup : inputMap.inputGroups()) {
            if (inputGroup.inputRemappingOption() ==
                    InputEnums.REMAP_OPTION_DISABLED) {
                continue;
            }
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputRemappingOption() !=
                        InputEnums.REMAP_OPTION_DISABLED) {
                    // Found InputAction remapped by user
                    processRemappedAction(inputAction);
                }
            }
        }
    }

    private void processRemappedAction(InputAction remappedInputAction) {
        // Get remapped action info
        InputControls remappedControls =
            remappedInputAction.remappedInputControls();
        List<Integer> remappedKeyCodes = remappedControls.keycodes();
        List<Integer> mouseActions = remappedControls.mouseActions();
        String version = remappedInputAction.inputActionId().versionString();
        long remappedActionId = remappedInputAction.inputActionId().uniqueId();
        Optional<InputAction> currentInputAction;
        if (version == null || version.isEmpty()
                    || version.equals(InputSDKProvider.INPUTMAP_VERSION)) {
            currentInputAction = getCurrentVersionInputAction(remappedActionId);
        } else {
            Log.i(TAG, "Detected version of user-saved input action defers " +
                    "from current version");
            currentInputAction =
                    getCurrentVersionInputActionFromPreviousVersion(
                            remappedActionId, version);
        }
        if (!currentInputAction.isPresent()) {
            Log.e(TAG, String.format(
                    "input action with id %d and version %s not found",
                    remappedActionId, version == null || version.isEmpty() ?
                            "UNKNOWN" : version));
            return;
        }
        InputControls originalControls =
                currentInputAction.get().inputControls();
        List<Integer> originalKeyCodes = originalControls.keycodes();

        Log.i(TAG, String.format(
                "Found input action with id %d remapped from key %s to key %s",
                remappedActionId,
                keyCodesToString(originalKeyCodes),
                keyCodesToString(remappedKeyCodes)));

        // TODO: make display changes to match controls used by the user
    }

    private Optional<InputAction> getCurrentVersionInputAction(
            long inputActionId) {
        for (InputGroup inputGroup :
                    InputSDKProvider.gameInputMap.inputGroups()) {
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputActionId().uniqueId() == inputActionId) {
                    return Optional.of(inputAction);
                }
            }
        }
        return Optional.empty();
    }

    private Optional<InputAction>
            getCurrentVersionInputActionFromPreviousVersion(
                    long inputActionId, String previousVersion) {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return Optional.empty();
    }

    private String keyCodesToString(List<Integer> keyCodes) {
        StringBuilder builder = new StringBuilder();
        for (Integer keyCode : keyCodes) {
            if (!builder.toString().isEmpty()) {
                builder.append(" + ");
            }
            builder.append(keyCode);
        }
        return String.format("(%s)", builder);
    }
}

#C

#if PLAY_GAMES_PC

using System.Text;
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;
using UnityEngine;

public class InputSDKRemappingListener : InputRemappingListenerCallbackHelper
{
    public override void OnInputMapChanged(InputMap inputMap)
    {
        Debug.Log("Received update on remapped controls.");
        if (inputMap.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED)
        {
            return;
        }
        List<InputGroup> inputGroups = inputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i ++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            if (inputGroup.InputRemappingOption()
                    == InputEnums.REMAP_OPTION_DISABLED)
            {
                continue;
            }
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j ++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputRemappingOption()
                        != InputEnums.REMAP_OPTION_DISABLED)
                {
                    // Found action remapped by user
                    ProcessRemappedAction(inputAction);
                }
            }
        }
    }

    private void ProcessRemappedAction(InputAction remappedInputAction)
    {
        InputControls remappedInputControls =
                remappedInputAction.RemappedInputControls();
        List<Integer> remappedKeycodes = remappedInputControls.Keycodes();
        List<Integer> mouseActions = remappedInputControls.MouseActions();
        string version = remappedInputAction.InputActionId().VersionString();
        long remappedActionId = remappedInputAction.InputActionId().UniqueId();
        InputAction currentInputAction;
        if (string.IsNullOrEmpty(version)
                || string.Equals(
                version, InputSDKMappingProvider.INPUT_MAP_VERSION))
        {
            currentInputAction = GetCurrentVersionInputAction(remappedActionId);
        }
        else
        {
            Debug.Log("Detected version of used-saved input action defers" +
                " from current version");
            currentInputAction =
                GetCurrentVersionInputActionFromPreviousVersion(
                    remappedActionId, version);
        }
        if (currentInputAction == null)
        {
            Debug.LogError(string.Format(
                "Input Action with id {0} and version {1} not found",
                remappedActionId,
                string.IsNullOrEmpty(version) ? "UNKNOWN" : version));
            return;
        }
        InputControls originalControls = currentInputAction.InputControls();
        List<Integer> originalKeycodes = originalControls.Keycodes();

        Debug.Log(string.Format(
            "Found Input Action with id {0} remapped from key {1} to key {2}",
            remappedActionId,
            KeyCodesToString(originalKeycodes),
            KeyCodesToString(remappedKeycodes)));
        // TODO: update HUD according to the controls of the user
    }

    private InputAction GetCurrentVersionInputAction(
            long inputActionId)
    {
        List<InputGroup> inputGroups =
            InputSDKMappingProvider.gameInputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputActionId().UniqueId() == inputActionId)
                {
                    return inputAction;
                }
            }
        }
        return null;
    }

    private InputAction GetCurrentVersionInputActionFromPreviousVersion(
            long inputActionId, string version)
    {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return null;
    }

    private string KeyCodesToString(List<Integer> keycodes)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < keycodes.Size(); i ++)
        {
            Integer keycode = keycodes.Get(i);
            if (builder.Length > 0)
            {
                builder.Append(" + ");
            }
            builder.Append(keycode.IntValue());
        }
        return string.Format("({0})", builder.ToString());
    }
}
#endif

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

الإعداد

إذا كنت تستخدم InputContexts، اضبط السياق في كل انتقال إلى مشهد جديد، بما في ذلك السياق الأول المستخدَم للمشهد الأوّلي. عليك ضبط InputContext بعد تسجيل InputMap.

إذا كنت تستخدم InputRemappingListeners لتلقّي إشعارات بشأن إعادة ربط الأحداث، عليك تسجيل InputRemappingListener قبل تسجيل InputMappingProvider، وإلا قد تفوت لعبتك أحداث مهمة أثناء وقت التشغيل.

يوضّح المثال التالي كيفية إعداد واجهة برمجة التطبيقات:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(InputSDKRemappingListener())
        inputMappingClient.setInputMappingProvider(
                InputSDKProvider())
        // Set the context after you have registered the provider.
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext)
    }
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(
                new InputSDKRemappingListener());
        inputMappingClient.setInputMappingProvider(
                new InputSDKProvider());
        // Set the context after you have registered the provider
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext);
    }
}

#C

#if PLAY_GAMES_PC
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.InputMapping.ExternalType.Android.Content;
using Google.LibraryWrapper.Java;
#endif

public class GameManager : MonoBehaviour
{
#if PLAY_GAMES_PC
    private InputSDKMappingProvider _inputMapProvider =
        new InputSDKMappingProvider();
    private InputMappingClient _inputMappingClient;
#endif

    public void Awake()
    {
#if PLAY_GAMES_PC
        Context context = (Context)Utils.GetUnityActivity().GetRawObject();
        _inputMappingClient = Google.Android.Libraries.Play.Games.Inputmapping
            .Input.GetInputMappingClient(context);
        // Register listener before registering the provider.
        _inputMappingClient.RegisterRemappingListener(
            new InputSDKRemappingListener());
        _inputMappingClient.SetInputMappingProvider(_inputMapProvider);
        // Register context after you have registered the provider.
       _inputMappingClient.SetInputContext(
           InputSDKMappingProvider.menuSceneInputContext);
#endif
    }
}

تَنظيم

يجب إلغاء تسجيل مثيل InputMappingProvider وأي مثيل InputRemappingListener عند إغلاق لعبتك، علمًا بأنّ حزمة تطوير البرامج (SDK) الخاصة بأداة Input ذكية بما يكفي لتجنُّب تسريب الموارد في حال عدم إلغاء التسجيل:

Kotlin

override fun onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        inputMappingClient.clearInputMappingProvider()
        inputMappingClient.clearRemappingListener()
    }

    super.onDestroy()
}

Java

@Override
protected void onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        inputMappingClient.clearInputMappingProvider();
        inputMappingClient.clearRemappingListener();
    }

    super.onDestroy();
}

#C

public class GameManager : MonoBehaviour
{
    private void OnDestroy()
    {
#if PLAY_GAMES_PC
        _inputMappingClient.ClearInputMappingProvider();
        _inputMappingClient.ClearRemappingListener();
#endif
    }
}

الاختبار

يمكنك اختبار تنفيذ حزمة تطوير البرامج (SDK) الخاصة بأداة Input من خلال فتح التراكب يدويًا لعرض تجربة المشغّل، أو من خلال adb shell لإجراء الاختبار والتحقّق بشكل آلي.

يتحقّق محاكي برنامج "ألعاب Google Play على الكمبيوتر" من صحة خريطة الإدخال الخاصة بك مقارنةً بالأخطاء الشائعة. في حالات مثل تكرار المعرّفات الفريدة أو استخدام خرائط إدخال مختلفة أو عدم استيفاء قواعد إعادة الربط (في حال تفعيل إعادة الربط)، تعرض الطبقة المتراكبة رسالة خطأ كما يلي: تراكب حزمة تطوير البرامج (SDK) الخاصة بأداة الإدخال

تحقَّق من صحة تنفيذ حزمة Input SDK باستخدام adb في سطر الأوامر. للحصول على خريطة الإدخال الحالية، استخدِم الأمر adb shell التالي (استبدِل MY.PACKAGE.NAME باسم لعبتك):

adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME

ستظهر لك نتيجة مشابهة لما يلي في حال تم تسجيل InputMap بنجاح:

Getting input map for com.example.inputsample...
Successfully received the following inputmap:
# com.google.android.libraries.play.games.InputMap@d73526e1
input_groups {
  group_label: "Basic Movement"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
    }
    unique_id: 0
  }
  input_actions {
    action_label: "Left"
    input_controls {
      keycodes: 29
      keycodes: 21
    }
    unique_id: 1
  }
  input_actions {
    action_label: "Right"
    input_controls {
      keycodes: 32
      keycodes: 22
    }
    unique_id: 2
  }
  input_actions {
    action_label: "Use"
    input_controls {
      keycodes: 33
      keycodes: 66
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 3
  }
}
input_groups {
  group_label: "Special Input"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
      keycodes: 62
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 4
  }
  input_actions {
    action_label: "Duck"
    input_controls {
      keycodes: 47
      keycodes: 20
      keycodes: 113
      mouse_actions: MOUSE_RIGHT_CLICK
      mouse_actions_value: 1
    }
    unique_id: 5
  }
}
mouse_settings {
  allow_mouse_sensitivity_adjustment: true
  invert_mouse_movement: true
}

Localization

لا تستخدم حزمة تطوير البرامج (SDK) الخاصة بأداة الإدخال نظام تحديد الموقع الجغرافي في Android. نتيجةً لذلك، يجب تقديم سلاسل مترجمة عند إرسال InputMap. يمكنك أيضًا استخدام نظام الترجمة في محرّك الألعاب.

Proguard

عند استخدام Proguard لتصغير حجم لعبتك، أضِف القواعد التالية إلى ملف إعدادات Proguard للتأكّد من عدم إزالة حزمة SDK من الحزمة النهائية:

-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }

الخطوات التالية

بعد دمج حزمة تطوير البرامج (SDK) الخاصة بأدوات التحكّم في لعبتك، يمكنك مواصلة استيفاء أي متطلبات متبقية من متطلبات "ألعاب Google Play على الكمبيوتر". لمزيد من المعلومات، يُرجى الاطّلاع على بدء استخدام "ألعاب Google Play على الكمبيوتر".