شروع به کار با «کیت توسعه نرم‌افزار ورودی»

این سند نحوه راه‌اندازی و نمایش «کیت توسعه نرم‌افزار ورودی» در بازی‌هایی را که از «بازی‌های Google Play در رایانه» پشتیبانی می‌کنند شرح می‌دهد. این وظایف شامل افزودن «کیت توسعه نرم‌افزار» به بازی و تولید نقشه ورودی است که حاوی تخصیص‌های کنش‌های بازی به ورودی کاربر است.

قبل از شروع به کار

قبل‌از افزودن «کیت توسعه نرم‌افزار ورودی» به بازی‌تان، باید از ورودی صفحه‌کلید و موشواره بااستفاده از سیستم ورودی موتور بازی‌تان پشتیبانی کنید.

«کیت توسعه نرم‌افزار ورودی» اطلاعاتی درباره کنترل‌هایی که بازی‌تان استفاده می‌کند به «بازی‌های Google Play در رایانه» ارائه می‌دهد تا این کنترل‌ها به کاربر نمایش داده شوند. همچنین می‌تواند به‌صورت اختیاری به کاربران اجازه دهد صفحه‌کلید را دوباره تخصیص دهند.

هر کنترل یک InputAction است (برای نمونه، «J» برای «پرش») و InputActions را در InputGroups سازمان‌دهی می‌کنید. InputGroup ممکن است نشان‌دهنده حالت متفاوتی در بازی شما باشد، مثل «رانندگی» یا «پیاده‌روی» یا «منو اصلی». همچنین می‌توانید از InputContexts برای نشان دادن اینکه کدام گروه‌ها در نقاط مختلف بازی فعال هستند استفاده کنید.

می‌توانید «تغییر تخصیص صفحه‌کلید» را فعال کنید تا به‌طور خودکار برایتان انجام شود، اما اگر ترجیح می‌دهید رابط تغییر تخصیص کنترل خودتان را ارائه دهید، می‌توانید «تغییر تخصیص کیت توسعه نرم‌افزار ورودی» را غیرفعال کنید.

نمودار توالی زیر نحوه عملکرد API «کیت توسعه نرم‌افزار ورودی» را شرح می‌دهد:

نمودار توالی پیاده‌سازی بازی که API کیت توسعه نرم‌افزار ورودی را فراخوانی می‌کند
و تعامل آن با دستگاه Android.

وقتی بازی‌تان «کیت توسعه نرم‌افزار ورودی» را پیاده‌سازی می‌کند، کنترل‌هایتان در رونهاد «بازی‌های Google Play در رایانه» نمایش داده می‌شود.

روپوش «بازی‌های Google Play در رایانه»

روپوش «بازی‌های Google Play در رایانه» («روپوش») کنترل‌های تعریف‌شده توسط بازی شما را نمایش می‌دهد. کاربران با فشار دادن کلید تبدیل + کلید جهش در هر زمانی به رونهاد دسترسی پیدا می‌کنند.

روپوش «بازی‌های Google Play در رایانه».

روال‌های مطلوب برای طراحی بست‌های کلید

هنگام طراحی کلیدهای میان‌بر، روال‌های مطلوب زیر را درنظر بگیرید:

  • InputActions خود را در InputGroups مرتبط منطقی گروه‌بندی کنید تا پیمایش و قابلیت شناسایی کنترل‌ها درطول بازی بهبود یابد.
  • هر InputGroup را حداکثر به یک InputContext اختصاص دهید. جزئیات دقیق InputMap منجر به تجربه بهتری برای پیمایش کنترل‌ها در رونهاد می‌شود.
  • برای هر نوع صحنه متفاوت در بازی‌تان، InputContext بسازید. معمولاً می‌توانید از یک InputContext برای همه صحنه‌های «منو مانند» استفاده کنید. برای هر بازی کوچکی در بازی‌تان یا برای کنترل‌های جایگزین برای یک صحنه، از InputContexts مختلف استفاده کنید.
  • اگر دو کنش برای استفاده از یک کلید در یک InputContext طراحی شده‌اند، از رشته برچسبی مثل «تعامل / شلیک» استفاده کنید.
  • اگر دو کلید برای اتصال به یک InputAction طراحی شده‌اند، از ۲ InputActions متفاوت استفاده کنید که همان کنش را در بازی شما انجام می‌دهند. می‌توانید از رشته برچسب یکسانی برای هر دو InputActions استفاده کنید، اما شناسه آن باید متفاوت باشد.
  • اگر کلید تغییردهنده به مجموعه‌ای از کلیدها اعمال می‌شود، به‌جای چند InputActions که کلید تغییردهنده را ترکیب می‌کنند، یک InputAction با کلید تغییردهنده داشته باشید (مثال: به‌جای Shift + W،‏ Shift + A،‏ Shift + S،‏ Shift + D، از Shift و W،‏ A،‏ S،‏ D استفاده کنید).
  • وقتی کاربر در فیلدهای نوشتاری می‌نویسد، تخصیص مجدد ورودی به‌طور خودکار غیرفعال می‌شود. برای پیاده‌سازی فیلدهای نوشتاری Android، روال‌های مطلوب را دنبال کنید تا مطمئن شوید که Android می‌تواند فیلدهای نوشتاری را در بازی‌تان شناسایی کند و از تداخل کلیدهای بازنگاری‌شده با آن‌ها جلوگیری کند. اگر بازی‌تان باید از فیلدهای نوشتاری غیرمعمول استفاده کند، می‌توانید از setInputContext() با InputContext حاوی فهرست خالی InputGroups برای غیرفعال کردن تخصیص مجدد به‌صورت دستی استفاده کنید.
  • اگر بازی‌تان از تخصیص مجدد پشتیبانی می‌کند، درنظر داشته باشید که تخصیص‌های کلید خود را به‌روز کنید. این عملیاتی حساس است که می‌تواند با نسخه‌های ذخیره‌شده کاربر تداخل داشته باشد. درصورت امکان، از تغییر شناسه‌های کنترل‌های موجود خودداری کنید.

ویژگی بازنگاری

«بازی‌های Google Play در رایانه» از تخصیص مجدد کنترل صفحه‌کلید براساس پیوند کلیدهایی که بازی شما بااستفاده از «کیت توسعه نرم‌افزار ورودی» ارائه می‌دهد پشتیبانی می‌کند. این اختیاری است و می‌تواند کاملاً غیرفعال شود. برای مثال، ممکن است بخواهید رابط بازنگاشت صفحه‌کلید خودتان را ارائه دهید. برای غیرفعال کردن تخصیص مجدد برای بازی‌تان، کافی است گزینه تخصیص مجدد را برای InputMap غیرفعال کنید (برای اطلاعات بیشتر، ساختن InputMap را ببینید).

برای دسترسی به این ویژگی، کاربران باید رونهاد را باز کنند و سپس روی کنشی که می‌خواهند مجدداً تخصیص دهند کلیک کنند. پس‌از هر رویداد تخصیص مجدد، «بازی‌های Google Play در رایانه» هر کنترل تخصیص‌داده‌شده مجدد توسط کاربر را به کنترل‌های پیش‌فرضی که بازی شما انتظار دریافت آن‌ها را دارد تخصیص می‌دهد، بنابراین بازی شما نیازی به آگاهی از تخصیص مجدد بازیکن ندارد. می‌توانید به‌صورت اختیاری دارایی‌های استفاده‌شده برای نمایش کنترل‌های صفحه‌کلید در بازی‌تان را با افزودن یک بازخوان برای تخصیص مجدد رویدادها به‌روز کنید.

تلاش برای تخصیص مجدد کلید

«بازی‌های Google Play در رایانه» کنترل‌های دوباره تخصیص‌داده‌شده را به‌صورت محلی برای هر کاربر ذخیره می‌کند و ماندگاری کنترل را در جلسات بازی امکان‌پذیر می‌کند. این اطلاعات فقط برای پلاتفرم رایانه در دیسک ذخیره می‌شود و بر تجربه تلفن همراه تأثیری ندارد. وقتی کاربر «بازی‌های Google Play در رایانه» را حذف نصب یا بازنصب می‌کند، داده‌های کنترل حذف می‌شود. این داده‌ها در چند دستگاه رایانه شخصی ماندگار نیست.

برای پشتیبانی از ویژگی تخصیص مجدد در بازی‌تان، از محدودیت‌های زیر اجتناب کنید:

محدودیت‌های تخصیص مجدد

اگر کلیدهای تخصیص‌داده‌شده حاوی هریک از موارد زیر باشد، ویژگی‌های تخصیص مجدد کلیدها می‌تواند در بازی غیرفعال شود:

  • ‫InputActions چندکلیدی که از کلید اصلاح‌گر + کلید غیر اصلاح‌گر تشکیل نشده‌اند. برای مثال، تبدیل + A معتبر است اما A + B، کنترل + دگرساز، یا تبدیل + A + جهش معتبر نیست.
  • ‫InputMap حاوی InputActions،‏ InputGroups، یا InputContexts با شناسه‌های یکتای تکراری است.

محدودیت‌های تخصیص مجدد

هنگام طراحی کلیدهای اختصاصی برای تخصیص مجدد، محدودیت‌های زیر را درنظر بگیرید:

  • تغییر تخصیص به ترکیب‌های کلید پشتیبانی نمی‌شود. برای مثال، کاربران نمی‌توانند تبدیل Shift + A به Ctrl + B یا A به Shift + A را انجام دهند.
  • تغییر تخصیص برای InputActions با دکمه‌های موشواره پشتیبانی نمی‌شود. برای مثال، تبدیل + کلیک راست را نمی‌توان دوباره تخصیص داد.

آزمایش کردن تخصیص مجدد کلید در «شبیه‌ساز بازی‌های Google Play در رایانه»

هرزمان بخواهید می‌توانید ویژگی تخصیص مجدد را در «شبیه‌ساز بازی‌های Google Play در رایانه» با اجرای دستور adb زیر فعال کنید:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

هم‌پوشانی تغییر می‌کند، همان‌طور که در تصویر زیر نشان داده شده است:

رونهاد با فعال بودن تخصیص مجدد کلید.

افزودن کیت توسعه نرم‌افزار

«کیت توسعه نرم‌افزار ورودی» را براساس پلاتفرم توسعه خود نصب کنید.

Java و Kotlin

با افزودن وابستگی به فایل build.gradle سطح واحد، Input SDK را برای Java یا Kotlin دریافت کنید:

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

Unity

«کیت توسعه نرم‌افزار ورودی» یک بسته استاندارد Unity با چندین وابستگی است.

نصب بسته با همه وابستگی‌ها الزامی است. چندین روش برای نصب بسته‌ها وجود دارد.

نصب .unitypackage

فایل 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 را نشان می‌دهد:

کاتلین

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

جاوا

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 غیرفعال کنید، اما «کیت توسعه نرم‌افزار ورودی» به‌اندازه کافی هوشمند است که اگر در InputMap از آن پشتیبانی نکنید، تخصیص مجدد را غیرفعال کند.

این مثال کلید فاصله را به کنش Drive اختصاص می‌دهد.

کاتلین

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

جاوا

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
);

«کنش ورودی» تک‌کلیدی در رونهاد نمایش داده می‌شود.

کنش‌ها می‌توانند نشان‌دهنده ورودی‌های موشواره نیز باشند. این مثال کلیک چپ را روی کنش انتقال تنظیم می‌کند:

کاتلین

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)
}

جاوا

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 شما مشخص می‌شوند. در این مثال، فاصله + مهار به کنش توربو تخصیص داده شده است که حتی وقتی فاصله به رانندگی تخصیص داده شده باشد کار می‌کند.

کاتلین

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)
}

جاوا

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
);

«کنش ورودی» چندکلیدی در رونهاد نمایش داده می‌شود.

«کیت توسعه نرم‌افزار ورودی» به شما امکان می‌دهد دکمه‌های موشواره و صفحه‌کلید را برای یک کنش واحد با هم ترکیب کنید. این مثال نشان می‌دهد که Shift و کلیک راست با هم فشار داده شده‌اند و یک نقطه مسیر در این بازی نمونه اضافه می‌کنند:

کاتلین

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)
}

جاوا

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 غیرفعال کنید، اما «کیت توسعه نرم‌افزار ورودی» به‌اندازه کافی هوشمند است که اگر در InputMap از آن پشتیبانی نکنید، تخصیص مجدد را غیرفعال کند.

کاتلین

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
  )
}

جاوا

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 را نشان می‌دهد:

کاتلین

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))
}

جاوا

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 استفاده می‌شود.

کاتلین

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()))
  )
}

جاوا

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: «گروه‌های ورودی» گزارش‌شده توسط بازی شما. گروه‌ها به‌ترتیب در رونهاد نمایش داده می‌شوند، مگراینکه گروه‌های درحال استفاده در تماس 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 اختصاص می‌دهد:

کاتلین

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)
  }
}

جاوا

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 برای ردیابی شناسه‌های یکتای شیء را نشان می‌دهد:

کاتلین

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

جاوا

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 را ثبت کنید:

کاتلین

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"
  }
}

جاوا

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 استفاده می‌کنید، بافت را در هر گذار به صحنه جدید تنظیم کنید، ازجمله اولین بافتی که برای صحنه اولیه استفاده شده است. پس‌از ثبت InputMap، باید InputContext را تنظیم کنید.

اگر از InputRemappingListeners برای دریافت اعلان درباره رویدادهای تخصیص مجدد استفاده می‌کنید، InputRemappingListener را قبل‌از ثبت InputMappingProvider ثبت کنید، درغیراین‌صورت ممکن است بازی شما رویدادهای مهم را درطول زمان راه‌اندازی ازدست بدهد.

نمونه زیر نحوه مقداردهی اولیه API را نشان می‌دهد:

کاتلین

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)
    }
}

جاوا

@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 را لغو ثبت کنید، اگرچه «کیت توسعه نرم‌افزار ورودی» به‌اندازه کافی هوشمند است که درصورت انجام ندادن این کار، از نشت منابع جلوگیری کند:

کاتلین

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

    super.onDestroy()
}

جاوا

@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
    }
}

آزمایش

می‌توانید پیاده‌سازی «کیت توسعه نرم‌افزار ورودی» را با باز کردن دستی رونهاد برای مشاهده تجربه پخش‌کننده، یا ازطریق پوسته adb برای آزمایش و درستی‌سنجی خودکار آزمایش کنید.

«شبیه‌ساز بازی‌های Google Play در رایانه» صحت نقشه ورودی شما را دربرابر خطاهای رایج بررسی می‌کند. برای سناریوهایی مثل شناسه‌های یکتای تکراری، استفاده از نقشه‌های ورودی مختلف، یا عدم موفقیت در قوانین تخصیص مجدد (اگر تخصیص مجدد فعال باشد)، رونهاد پیام خطایی را به‌صورت زیر نشان می‌دهد: رونهاد «کیت توسعه نرم‌افزار ورودی».

پیاده‌سازی «کیت توسعه نرم‌افزار ورودی» را بااستفاده از 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
}

بومی‌سازی

«کیت توسعه نرم‌افزار ورودی» از سیستم بومی‌سازی Android استفاده نمی‌کند. درنتیجه، هنگام ارسال InputMap باید رشته‌های بومی‌سازی‌شده ارائه دهید. همچنین می‌توانید از سیستم بومی‌سازی موتور بازی‌تان استفاده کنید.

Proguard

هنگام استفاده از Proguard برای کوچک کردن بازی، قوانین زیر را به فایل پیکربندی proguard اضافه کنید تا مطمئن شوید کیت توسعه نرم‌افزار از بسته نهایی شما حذف نمی‌شود:

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

قدم بعدی چیست

پس‌از ادغام «کیت توسعه نرم‌افزار ورودی» در بازی‌تان، می‌توانید پیش‌نیازهای باقی‌مانده «بازی‌های Google Play در رایانه» را تکمیل کنید. برای اطلاعات بیشتر، به شروع به کار با «بازی‌های Google Play در رایانه» مراجعه کنید.