鼠标输入

本主题介绍了在输入转换模式不能提供理想的玩家体验时,如何针对 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) 以接收原始鼠标输入。

在上下文菜单中选中“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_ENTERACTION_HOVER_EXITACTION_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_DOWNACTION_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()