קלט העכבר

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

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

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

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

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

הטיפול בקלט ב-Google Play Games במחשב זהה לטיפול בקלט ב-ChromeOS. השינויים שתומכים במחשבים משפרים גם את המשחק לכל השחקנים ב-Android.

השבתת מצב תרגום הקלט

בקובץ AndroidManifest.xml, צריך להצהיר על android.hardware.type.pc התכונה. המשמעות היא שהמשחק משתמש בחומרה של המחשב ומשבית את מצב תרגום הקלט. בנוסף, הוספת required="false" עוזרת לוודא שאפשר עדיין להתקין את המשחק בטלפונים ובטאבלטים ללא עכבר. לדוגמה:

<manifest ...>
  <uses-feature
      android:name="android.hardware.type.pc"
      android:required="false" />
  ...
</manifest>

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

צילום מסך של האפשרות &#39;מצב מחשב(KiwiMouse)&#39; שנבחרה בתפריט ההקשר

אחרי שעושים את זה, תנועת העכבר מדווחת על ידי View.onGenericMotionEvent עם המקור SOURCE_MOUSE שמציין שזה אירוע של עכבר.

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
    var handled = false
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        // handle the mouse event here
        handled = true
    }
    handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        // handle the mouse event here
        return true;
    }
    return false;
});

פרטים על טיפול בקלט של העכבר זמינים במאמרי העזרה של ChromeOS.

טיפול בתנועות העכבר

כדי לזהות תנועת עכבר, מאזינים לאירועים ACTION_HOVER_ENTER, ACTION_HOVER_EXIT ו-ACTION_HOVER_MOVE.

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

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when(motionEvent.action) {
           MotionEvent.ACTION_HOVER_ENTER -> Log.d("MA", "Mouse entered at ${motionEvent.x}, ${motionEvent.y}")
           MotionEvent.ACTION_HOVER_EXIT -> Log.d("MA", "Mouse exited at ${motionEvent.x}, ${motionEvent.y}")
           MotionEvent.ACTION_HOVER_MOVE -> Log.d("MA", "Mouse hovered at ${motionEvent.x}, ${motionEvent.y}")
       }
       handled = true
   }

   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_HOVER_ENTER:
                Log.d("MA", "Mouse entered at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_HOVER_EXIT:
                Log.d("MA", "Mouse exited at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                Log.d("MA", "Mouse hovered at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
        }
        return true;
    }
    return false;
});

טיפול בכפתורי העכבר

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

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

בדוגמה הזו, נעשה שימוש ב-enum כדי להציג את התוצאה של getActionButton:

Kotlin

enum class MouseButton {
   LEFT,
   RIGHT,
   UNKNOWN;
   companion object {
       fun fromMotionEvent(motionEvent: MotionEvent): MouseButton {
           return when (motionEvent.actionButton) {
               MotionEvent.BUTTON_PRIMARY -> LEFT
               MotionEvent.BUTTON_SECONDARY -> RIGHT
               else -> UNKNOWN
           }
       }
   }
}

Java

enum MouseButton {
    LEFT,
    RIGHT,
    MIDDLE,
    UNKNOWN;
    static MouseButton fromMotionEvent(MotionEvent motionEvent) {
        switch (motionEvent.getActionButton()) {
            case MotionEvent.BUTTON_PRIMARY:
                return MouseButton.LEFT;
            case MotionEvent.BUTTON_SECONDARY:
                return MouseButton.RIGHT;
            default:
                return MouseButton.UNKNOWN;
        }
    }
}

בדוגמה הזו, הטיפול בפעולה דומה לטיפול באירועי ריחוף:

Kotlin

// Handle the generic motion event
gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when (motionEvent.action) {
           MotionEvent.ACTION_BUTTON_PRESS -> Log.d(
               "MA",
               "${MouseButton.fromMotionEvent(motionEvent)} pressed at ${motionEvent.x}, ${motionEvent.y}"
           )
           MotionEvent.ACTION_BUTTON_RELEASE -> Log.d(
               "MA",
               "${MouseButton.fromMotionEvent(motionEvent)} released at ${motionEvent.x}, ${motionEvent.y}"
           )
       }
       handled = true
   }

   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_BUTTON_PRESS:
                Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " pressed at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_BUTTON_RELEASE:
                Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " released at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
        }
        return true;
    }
    return false;
});

טיפול בגלילה באמצעות גלגל העכבר

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

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

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when (motionEvent.action) {
           MotionEvent.ACTION_SCROLL -> {
               val scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL)
               val scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL)
               Log.d("MA", "Mouse scrolled $scrollX, $scrollY")
           }
       }
       handled = true
   }
   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_SCROLL:
                float scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
                float scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
                Log.d("MA", "Mouse scrolled " + scrollX + ", " + scrollY);
                break;
        }
        return true;
    }
    return false;
});

תיעוד קלט העכבר

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

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

Kotlin

override fun onWindowFocusChanged(hasFocus: Boolean) {
   super.onWindowFocusChanged(hasFocus)

   if (hasFocus) {
       gameView.requestPointerCapture()
   }
}

Java

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    if (hasFocus) {
        View gameView = findViewById(R.id.game_view);
        gameView.requestPointerCapture();
    }
}

אירועים שמתועדים על ידי requestPointerCapture() נשלחים לתצוגה שניתן להתמקד בה ושנרשמה OnCapturedPointerListener. לדוגמה:

Kotlin

gameView.focusable = View.FOCUSABLE
gameView.setOnCapturedPointerListener { _, motionEvent ->
    Log.d("MA", "${motionEvent.x}, ${motionEvent.y}, ${motionEvent.actionButton}")
    true
}

Java

gameView.setFocusable(true);
gameView.setOnCapturedPointerListener((view, motionEvent) -> {
    Log.d("MA", motionEvent.getX() + ", " + motionEvent.getY() + ", " + motionEvent.getActionButton());
    return true;
});

כדי לבטל את הלכידה הבלעדית של העכבר, למשל כדי לאפשר לשחקנים ליצור אינטראקציה עם תפריט ההשהיה, מפעילים את View.releasePointerCapture().