טיפול בשינויים בקלט

משתמשי Chromebook יכולים לבחור מתוך מגוון רחב של אפשרויות קלט: מקלדת, עכבר, משטחי מגע, מסכי מגע, סטיילוס, MIDI ובקר משחקים/בקרים עם Bluetooth. כלומר, אותו מכשיר יכול להפוך לעמדת DJ, ללוח ציור של אמן או לפלטפורמה המועדפת על גיימרים לסטרימינג של משחקי AAA.

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

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

גילוי שיטות קלט נתמכות על ידי המשתמש

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

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

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

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

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

תוויות או פריסה של ממשק המשתמש עבור וריאציות של קלט

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

ממשק משתמש שמותאם למגע

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

ממשקי משתמש של משחקי מרוצי מכוניות – אחד עם אמצעי בקרה במסך ואחד עם מקלדת

הנחיות במסך

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

  • החלקה אל…
  • יש ללחוץ בכל נקודה שהיא כדי לסגור
  • שינוי מרחק התצוגה באמצעות צביטה
  • לוחצים על X כדי…
  • לחיצה ארוכה להפעלה

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

שיקולים לגבי כמה שפות

אם האפליקציה תומכת בכמה שפות, כדאי לתכנן את ארכיטקטורת המחרוזות. לדוגמה, אם אתם תומכים ב-3 מצבי קלט וב-5 שפות, יכול להיות שיהיו 15 גרסאות שונות של כל הודעת ממשק משתמש. השינוי הזה יגדיל את כמות העבודה שנדרשת כדי להוסיף תכונות חדשות, ויגדיל את הסבירות לשגיאות איות.

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

קלט במקלדת וירטואלית / IME

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

הטמעה

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

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

מכונת מצבים עם עדיפות שמבוססת על אירועים שהתקבלו

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

תעדוף לפי

למה כדאי לתעדף מצבי קלט? המשתמשים יוצרים אינטראקציה עם המכשירים שלהם בכל מיני דרכים, והאפליקציה שלכם צריכה לתמוך בבחירה שלהם. לדוגמה, אם משתמש בוחר להשתמש במסך המגע ובעכבר Bluetooth בו-זמנית, המערכת צריכה לתמוך בכך. אבל אילו הנחיות ופקדים להזנת נתונים במסך כדאי להציג? עכבר או מגע?

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

אירועי קלט שהתקבלו קובעים את המצב

למה כדאי לפעול רק על אירועי קלט שהתקבלו? יכול להיות שאתם חושבים שאפשר לעקוב אחרי חיבורי Bluetooth כדי לדעת אם בקר Bluetooth מחובר, או לעקוב אחרי חיבורי USB כדי לדעת אם מכשירי USB מחוברים. לא מומלץ להשתמש בגישה הזו משתי סיבות עיקריות.

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

סיבה נוספת לשימוש באירועים שהתקבלו במקום בסטטוס החיבור היא שייתכן שלמשתמשים יהיה עכבר, בקר Bluetooth, בקר MIDI וכו' שמחוברים למכשיר, אבל הם לא משתמשים בהם באופן פעיל כדי לבצע אינטראקציה עם האפליקציה או המשחק.

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

דוגמה: משחק עם תמיכה במגע, במקלדת/עכבר ובבקר

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

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

רוצים להוסיף תמיכה במקלדת ובשלט Bluetooth למשחקים. איפה מתחילים?

משחק מרוצי מכוניות עם פקדי מגע

מצבי קלט

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

                                                                                                                                                                        
מגעמקלדת או עכברבקר משחק
       

תגובות באמוג'י ל

     
       

כל הקלט

     
       

כל הקלט

     
       

כל הקלט

     
       

הפקדים במסך

     
       

– ג'ויסטיק וירטואלי
– כפתור Nitro

     
       

‫- ללא ג'ויסטיק
- ללא כפתור ניטרו

     
       

‫- ללא ג'ויסטיק
- ללא כפתור ניטרו

     
       

טקסט

     
       

כדאי ללחוץ כדי להירשם ל-Nitro.

     
       

לוחצים על N כדי להפעיל את Nitro.

     
       

מקישים על A כדי להפעיל את Nitro.

     
       

מדריך

     
       

תמונה של ג'ויסטיק למהירות ולכיוון

     
       

תמונה של מקשי החיצים ו-WASD לשינוי מהירות וכיוון

     
       

תמונה של בקר משחקים לשינוי מהירות או כיוון

     

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

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

שינויים במצב שקיבלו עדיפות

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

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

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

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

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

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

מכונת מצבים עם עדיפות – מסך מגע, מקלדת/עכבר, בקר משחקים

                                                                                                                                        
‫#1 מסך מגע#2 Keyboard‫#3 Gamepad
       

העברה למקום הראשון

     
       

לא רלוונטי

     
       

– קלט מגע התקבל
– מעבר מיידי למצב קלט מגע

     
       

– קלט מגע התקבל
– מעבר מיידי למצב קלט מגע

     
       

העברה אל #2

     
       

– אין מגע למשך 5 שניות
– מתקבל קלט מהמקלדת
– מעבר למצב קלט מהמקלדת

     
       

לא רלוונטי

     
       

‫- Keyboard input received
(move immediately to Keyboard input state)

     
       

העברה אל #3

     
       

– אין מגע במשך 5 שניות
– אין קלט מהמקלדת במשך 5 שניות
– התקבל קלט מהגיימפאד
– מעבר למצב קלט מהגיימפאד

     
       

– אין קלט מהמקלדת במשך 5 שניות
– מתקבל קלט מהגיימפאד
– מעבר למצב קלט מהגיימפאד

     
       

לא רלוונטי

     

הערה: שימו לב איך סדר העדיפויות עוזר להבין בבירור איזה סוג של קלט צריך להיות דומיננטי. מצב הקלט עולה באופן מיידי בסדר העדיפויות:

3. לוח משחק -> 2. מקלדת -> 1. מגע

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

אירועי קלט

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

לחצנים במקלדת ובבקר

// Drive the state machine based on the received input type
// onKeyDown drives the state machine, but does not trigger game actions
// Both keyboard and game controller events come through as key events
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (event != null) {
        // Check input source
        val outputMessage = when (event.source) {
            SOURCE_KEYBOARD -> {
                MyStateMachine.KeyboardEventReceived()
                "Keyboard event"
            }
            SOURCE_GAMEPAD -> {
                MyStateMachine.ControllerEventReceived()
                "Game controller event"
            }
            else -> "Other key event: ${event.source}"
        }
        // Do something based on source type
        findViewById(R.id.text_message).text = outputMessage
    }
    // Pass event up to system
    return super.onKeyDown(keyCode, event)
}
// Trigger game events based on key release
// Both keyboard and game controller events come through as key events
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
   when(keyCode) {
       KeyEvent.KEYCODE_N -> {
           MyStateMachine.keyboardEventReceived()
           engageNitro()
           return true // event handled here, return true
       }
   }
   // If event not handled, pass up to system
   return super.onKeyUp(keyCode, event)
}

הערה: כשמדובר ב-KeyEvents, אפשר להשתמש ב-onKeyDown() או ב-onKeyUp(). בדוגמה הזו, onKeyDown() משמש לשליטה במכונת המצבים, ואילו onKeyUp() משמש להפעלת אירועים במשחק.

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

מגע וסטיילוס

// Touch and stylus events come through as touch events
override fun onTouchEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Get tool type
       val pointerIndex = event.action and ACTION_POINTER_INDEX_MASK shr ACTION_POINTER_INDEX_SHIFT
       val toolType = event.getToolType(pointerIndex)

       // Check tool type
       val outputMessage = when (toolType) {
           TOOL_TYPE_FINGER -> {
               MyStateMachine.TouchEventReceived()
               "Touch event"
           }
           TOOL_TYPE_STYLUS -> {
                MyStateMachine.StylusEventReceived()
               "Stylus event"
           }
           else -> "Other touch event: ${toolType}"
       }

       // Do something based on tool type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}

עכבר וג'ויסטיק

// Mouse and joystick events come through as generic events
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Check input source
       val outputMessage = when (event.source) {
           SOURCE_JOYSTICK -> {
                MyStateMachine.ControllerEventReceived()
               "Controller event"
           }
           SOURCE_MOUSE -> {
                MyStateMachine.MouseEventReceived()
               "Mouse event"
           }
           else -> "Other generic event: ${event.source}"
       }
       // Do something based on source type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}