תחילת העבודה עם קלט 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:

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

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

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

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

‫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

מורידים את קובץ Input SDK unitypackage עם כל יחסי התלות שלו. כדי להתקין את .unitypackage, בוחרים באפשרות Assets > Import package > Custom Package (נכסים > ייבוא חבילה > חבילה מותאמת אישית) ומאתרים את הקובץ שהורדתם.

התקנה באמצעות 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

בדוגמה הזו, מקש הרווח ממופה לפעולה Drive (נסיעה).

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, שפועלת גם כשמקש הרווח ממופה ל-Drive.

Kotlin

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

Java

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

C#‎

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

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

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

בדוגמה הבאה מוצגות קבוצות הקלט Road controls ו-Menu controls בשכבת העל:

שכבת העל מציגה InputMap שמכיל את קבוצות הקלט של Road controls ו-Menu controls.

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

קבלת התראות על מיפוי מחדש של אירועים (אופציונלי)

קבלת התראות על אירועי מיפוי מחדש כדי לקבל מידע על המקשים שבהם נעשה שימוש במשחק. כך המשחק יכול לעדכן את הנכסים שמוצגים במסך המשחק, שמשמשים להצגת אמצעי הבקרה של הפעולה.

בתמונה הבאה מוצגות דוגמאות להתנהגות הזו. אחרי מיפוי מחדש של המקשים G, ‏ P ו-S למקשים J, ‏ X ו-T בהתאמה, רכיבי ממשק המשתמש של המשחק מתעדכנים ומציגים את המקשים שהמשתמש הגדיר.

ממשק משתמש שמגיב למיפוי מחדש של אירועים באמצעות הקריאה החוזרת InputRemappingListener.

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

Kotlin

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

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

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

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

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

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

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

Java

public class InputSDKRemappingListener implements InputRemappingListener {

    private static final String TAG = "InputSDKRemappingListener";

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

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

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

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

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

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

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

C#‎

#if PLAY_GAMES_PC

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

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

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

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

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

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

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

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

אתחול

אם אתם משתמשים ב-InputContexts, צריך להגדיר את ההקשר בכל מעבר לסצנה חדשה, כולל ההקשר הראשון שבו השתמשתם בסצנה הראשונית. צריך להגדיר את InputContext אחרי שרושמים את InputMap.

אם אתם משתמשים ב-InputRemappingListeners כדי לקבל התראה על מיפוי מחדש של אירועים, עליכם לרשום את InputRemappingListener לפני שאתם רושמים את InputMappingProvider. אחרת, יכול להיות שהמשחק שלכם יפספס אירועים חשובים בזמן ההפעלה.

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

Localization

‫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 במחשב.