本主题介绍了如何在适用于电脑的 Google Play 游戏中实现鼠标输入,适用于输入转换模式无法提供理想玩家体验的游戏。
PC 玩家通常使用键盘和鼠标而非触摸屏,因此考虑游戏是否支持鼠标输入非常重要。默认情况下,适用于电脑的 Google Play 游戏会将任何鼠标左键点击事件转换为单个虚拟轻触事件。这称为“输入转换模式”。
尽管此模式只需少量更改即可使您的游戏正常运行,但它无法为 PC 玩家提供原生体验。为此,我们建议您实现以下功能:
- 上下文菜单的悬停状态,而非按住操作
- 右键点击用于在长按或上下文菜单中发生的替代操作
- 第一人称或第三人称动作游戏的鼠标视角,而非按住并拖动事件
为了支持 PC 上常见的界面模式,您必须停用输入转换模式。
适用于电脑的 Google Play 游戏的输入处理与 ChromeOS 的输入处理相同。支持 PC 的更改也会改善所有 Android 玩家的游戏体验。
停用输入转换模式
在您的 AndroidManifest.xml
文件中,声明 android.hardware.type.pc
特性。这表示您的游戏使用 PC 硬件并停用输入转换模式。此外,添加 required="false"
有助于确保您的游戏仍然可以在没有鼠标的手机和平板电脑上安装。例如
<manifest ...>
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
...
</manifest>
适用于电脑的 Google Play 游戏的生产版本在游戏启动时会切换到正确的模式。在开发者模拟器中运行时,您需要右键点击任务栏图标,选择 Developer Options,然后选择 PC mode(KiwiMouse) 以接收原始鼠标输入。
执行此操作后,鼠标移动将由 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
来获取所有按钮的状态。
在此示例中,使用枚举来帮助显示 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()
仅在包含您的视图的视图层次结构具有焦点时才有效。因此,您无法在 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()
。