滑鼠輸入

本主題說明在遊戲的輸入轉譯模式未提供理想玩家體驗時,如何為 Google Play 遊戲電腦版實作滑鼠輸入方法。

電腦玩家通常不會使用觸控螢幕,而是使用鍵盤和滑鼠,因此務必考量遊戲能否使用滑鼠輸入。根據預設,Google Play 遊戲電腦版會將按下滑鼠左鍵的事件轉換為輕觸一下的虛擬事件。這就是「輸入轉譯模式」。

這個模式雖然可以讓您不用修改遊戲太多內容即可正常運作,但是也無法為電腦玩家提供十分原生的體驗。因此,建議您採用以下方法:

  • 讓內容選單使用懸停狀態,而不要使用按住操作
  • 當長按右鍵,或在內容選單內按右鍵時,進行替代操作
  • 第一或第三人稱動作遊戲採用 Mouselook,而不要使用按住拖曳事件

如果想支援電腦常見的 UI 模式,務必停用輸入轉譯模式。

Google Play 遊戲電腦版的輸入處理方式與 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 遊戲電腦版的正式版會切換為正確的模式。若是在開發人員模擬器中執行,您需要在工作列圖示上按一下滑鼠右鍵,依序選取「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;
});

處理滑鼠按鈕

電腦長久以來都使用左右滑鼠按鍵對互動元素進行主要及次要操作。在遊戲內,在按鈕上輕觸的操作最適合對應為按下左鍵,而按住操作則最適合使用右鍵。若是即時戰略遊戲,您也可以用左鍵選取,然後用右鍵移動。第一人稱射擊遊戲可將主要和次要射擊指派到左鍵和右鍵。無限跑酷遊戲可以用左鍵跳躍,並用右鍵衝刺。我們尚未新增對於中間點擊事件的支援。

如果想處理按鈕按下的操作,請用 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_VSCROLLAXIS_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()