קלט העכבר

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

בדרך כלל, שחקנים במחשב PC משתמשים במקלדת ובעכבר במקום במסך מגע, ולכן חשוב לבדוק אם המשחק שלכם תומך בקלט מהעכבר. כברירת מחדל, 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 במחשב עוברת למצב הנכון כשמשחק מופעל. כשמריצים את האפליקציה במהנתח למפתחים, צריך ללחוץ לחיצה ימנית על הסמל של שורת המשימות, לבחור באפשרות Developer Options ואז באפשרות PC mode(KiwiMouse) כדי לקבל קלט גולמי מהעכבר.

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

אחרי שתעשו זאת, תנועת העכבר תדווח על ידי View.onGeneralMotionEvent עם המקור 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;
});

טיפול בלחצני העכבר

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

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