תחילת העבודה עם קלט SDK

במסמך הזה מוסבר איך להגדיר ולהציג את Input SDK במשחקים שתומכים ב-Google Play Games במחשב. המשימות כוללות הוספת ה-SDK למשחק ויצירת מפת קלט שמכילה את ההקצאות של פעולות המשחק לקלט המשתמש.

לפני שמתחילים

לפני שמוסיפים את Input SDK למשחק, צריך לתמוך בהזנה ממקלדת ועכבר באמצעות מערכת הקלט של מנוע המשחק.

Input SDK מספק ל-Google Play Games במחשב מידע על אמצעי הבקרה שבהם המשחק משתמש, כדי שניתן יהיה להציג אותם למשתמש. אפשר גם לאפשר למשתמשים למפות מחדש את המקלדת.

כל אמצעי בקרה הוא InputAction (למשל, 'J' עבור 'קפיצה'), ואתם יכולים לארגן את ה-InputActions ב-InputGroups. האפשרות InputGroup עשויה לייצג מצב אחר במשחק, כמו 'נהיגה', 'הליכה' או 'תפריט ראשי'. אפשר גם להשתמש ב-InputContexts כדי לציין אילו קבוצות פעילות בנקודות שונות במשחק.

אתם יכולים להפעיל את הטיפול האוטומטי במיפוי מחדש של המקלדת, אבל אם אתם מעדיפים לספק ממשק משלכם למיפוי מחדש של אמצעי הבקרה, תוכלו להשבית את המיפוי מחדש של Input SDK.

בתרשים התהליך הבא מתואר האופן שבו פועל ה-API של Input SDK:

תרשים רצף של הטמעת משחק שמפעיל את Input SDK API ואת האינטראקציה שלו עם מכשיר Android.

כשמטמיעים את Input SDK במשחק, אמצעי הבקרה מוצגים בשכבת-העל של Google Play Games במחשב.

שכבת-העל של Google Play Games במחשב

בשכבת-העל של Google Play Games במחשב ('שכבת-העל') מוצגים הפקדים שמוגדרים במשחק. המשתמשים יכולים לגשת לשכבת-העל בכל שלב בלחיצה על Shift + Tab.

שכבת-העל של Google Play Games במחשב.

שיטות מומלצות לתכנון קישורי מפתחות

כשאתם מתכננים את קישורי המפתחות, כדאי להשתמש בשיטות המומלצות הבאות:

  • כדאי לקבץ את ה-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 Games במחשב יש תמיכה במיפוי מחדש של אמצעי הבקרה במקלדת על סמך קישורי המפתחות שהמשחק מספק באמצעות Input SDK. האפשרות הזו היא אופציונלית ואפשר להשבית אותה לגמרי. לדוגמה, תוכלו לספק ממשק משלכם למיפוי מחדש של המקלדת. כדי להשבית את המיפוי מחדש במשחק, פשוט צריך לציין את אפשרות המיפוי מחדש שהושבתה עבור InputMap (מידע נוסף זמין במאמר יצירת InputMap).

כדי לגשת לתכונה הזו, המשתמשים צריכים לפתוח את שכבת-העל וללחוץ על הפעולה שרוצים למפות מחדש. אחרי כל אירוע של שינוי מפה, Google Play Games במחשב ממפה כל אחת מהפקדים שהמשתמש שינה למפות ברירת המחדל שהמשחק מצפה לקבל, כך שהמשחק לא צריך להיות מודע לשינוי המפה של השחקן. אם רוצים לעדכן את הנכסים שמשמשים להצגת אמצעי הבקרה במקלדת במשחק, אפשר להוסיף קריאה חוזרת (callback) כדי למפות מחדש את האירועים.

מנסים למפות מחדש את המקש

ב-Google Play Games במחשב, השינויים במיפוי של אמצעי הבקרה נשמרים באופן מקומי לכל משתמש, כדי שהם יישארו בתוקף בסשנים שונים של משחק. המידע הזה נשמר בדיסק רק בפלטפורמת המחשב, והוא לא משפיע על חוויית השימוש בנייד. נתוני הבקרה נמחקים כשהמשתמש מסיר את Google Play Games מהמחשב או מתקין אותה מחדש. הנתונים האלה לא נשמרים במספר מחשבים.

כדי לתמוך בתכונה של מיפוי מחדש במשחק, יש להימנע מההגבלות הבאות:

הגבלות על מיפוי מחדש

יכול להיות שתכונות המיפוי מחדש יושבתו במשחק אם קישורי המקשים מכילים את המקרים הבאים:

  • מקש 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 Games במחשב

אפשר להפעיל את התכונה 'מיפוי מחדש' ב-Google Play Games במחשב בזמן אמת באמצעות הפקודה הבאה של adb:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

שכבת-העל תשתנה כמו בתמונה הבאה:

שכבת-העל עם הפעלת המיפוי מחדש של המקשים.

הוספת ה-SDK

מתקינים את Input SDK בהתאם לפלטפורמת הפיתוח.

Java ו-Kotlin

כדי לקבל את Input SDK ל-Java או ל-Kotlin, מוסיפים יחסי תלות לקובץ build.gradle ברמת המודול:

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

Unity

Input SDK היא חבילה רגילה של Unity עם כמה יחסי תלות.

חובה להתקין את החבילה עם כל יחסי התלות. יש כמה דרכים להתקין את החבילות.

התקנה של .unitypackage

מורידים את קובץ ה-unitypackage של Input SDK עם כל יחסי התלות שלו. כדי להתקין את .unitypackage, בוחרים באפשרות נכסים > ייבוא חבילה > חבילה מותאמת אישית ומאתרים את הקובץ שהורדתם.

התקנה באמצעות UPM

לחלופין, אפשר להתקין את החבילה באמצעות Unity Package Manager. לשם כך, מורידים את .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, אבל Input 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
);

פעולת קלט של מקש יחיד שמוצגת בשכבת-העל.

פעולות יכולות לייצג גם קלט של עכבר. בדוגמה הזו, הלחצן לחיצה ימנית מוגדר לפעולה העברה:

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

פעולת קלט של עכבר שמוצגת בשכבת-העל.

כדי לציין שילובי מקשים, מעבירים כמה קודי מפתחות ל-InputAction. בדוגמה הזו, המקש מקש הרווח + Shift ממופה לפעולה Turbo, שפועלת גם כשמקש הרווח ממופה להאצהרה.

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

פעולת קלט של כמה מפתחות שמוצגת בשכבת-העל.

באמצעות Input 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, אבל Input 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: קבוצות הקלט שדווחו על ידי המשחק. הקבוצות מוצגות בסדר בשכבת-העל, אלא אם צוינו הקבוצות הנוכחיות בשימוש בקריאה ל-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, בהתאמה, רכיבי ממשק המשתמש של המשחק מתעדכנים כך שיציגו את המקשים שהמשתמש הגדיר.

ממשק המשתמש מגיב לאירועי מיפוי מחדש באמצעות פונקציית ה-callback של 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. אחרת, יכול להיות שהמשחק שלכם יפספס אירועים חשובים במהלך ההשקה.

בדוגמה הבאה מוסבר איך לאתחל את ה-API:

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 כשהמשחק סגור, למרות ש-Input SDK מספיק חכם כדי למנוע דליפת משאבים אם לא תעשו זאת:

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

בדיקה

כדי לבדוק את הטמעת Input SDK, אפשר לפתוח באופן ידני את השכבה העליונה כדי לראות את חוויית השימוש בנגן, או להשתמש ב-adb shell כדי לבצע בדיקה ואימות אוטומטיים.

אמולטור Google Play Games במחשב בודק את תקינות מפת הקלט שלכם מול שגיאות נפוצות. בתרחישים כמו כפילויות של מזהי מקור נתונים ייחודיים, שימוש במפות קלט שונות או כשל בכללי המיפוי מחדש (אם המיפוי מחדש מופעל), תוצג הודעת שגיאה ב-Overlay, כמו זו שבהמשך: שכבת-העל של Input 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
}

התאמה לשוק המקומי

ב-Input SDK לא נעשה שימוש במערכת הלוקליזציה של Android. לכן, כששולחים קובץ InputMap, צריך לספק מחרוזות מותאמות לשוק המקומי. אפשר גם להשתמש במערכת הלוקליזציה של מנוע המשחק.

Proguard

כשמשתמשים ב-Proguard כדי לבצע אופטימיזציה למשחק, מוסיפים את הכללים הבאים לקובץ התצורה של Proguard כדי לוודא שה-SDK לא יוסר מהחבילה הסופית:

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

השלב הבא

אחרי שמשלבים את Input SDK במשחק, אפשר להמשיך ולעמוד בדרישות הנותרות של Google Play Games במחשב. מידע נוסף זמין במאמר תחילת העבודה עם Google Play Games במחשב.