支援多個遊戲控制器

雖然大多數遊戲都是為每部 Android 裝置支援單一使用者,但 API 也能支援多位使用者,透過遊戲控制器同時連線到同一部 Android 裝置。

本課程會說明從多個連接控制器處理單一裝置多人遊戲遊戲中的輸入資料。包括維持玩家顯示圖片與各控制器裝置之間的對應關係,並妥善處理控制器輸入事件。

將玩家對應至控制器裝置 ID

當遊戲控制器連線至 Android 裝置時,系統會指派一個整數的裝置 ID。您可以呼叫 InputDevice.getDeviceIds() 來取得已連結遊戲控制器的裝置 ID,如「驗證遊戲控制器已連線」一文所示。然後,您可以將每部裝置 ID 與遊戲中的玩家建立關聯,然後分別處理每位玩家的遊戲動作。

注意: 在搭載 Android 4.1 (API 級別 16) 以上版本的裝置上,您可以使用 getDescriptor() 取得輸入裝置的描述元,此方法會傳回輸入裝置專屬的永久字串值。與裝置 ID 不同的是,即使輸入裝置中斷連線、重新連線或重新設定,描述元值也不會變更。

下方程式碼片段說明如何使用 SparseArray,將玩家的顯示圖片與特定控制器建立關聯。在這個範例中,mShips 變數會儲存 Ship 物件集合。當使用者附加新的控制器時,系統會在遊戲中建立新的玩家顯示圖片,並在相關聯的控制器移除時移除。

onInputDeviceAdded()onInputDeviceRemoved() 回呼方法是「 跨 Android 版本支援控制器」中引入的抽象層的一部分。透過實作這些事件監聽器回呼,遊戲就能在新增或移除控制器時識別遊戲控制器的裝置 ID。這項偵測作業與 Android 2.3 (API 級別 9) 以上版本相容。

Kotlin

private val ships = SparseArray<Ship>()

override fun onInputDeviceAdded(deviceId: Int) {
    getShipForID(deviceId)
}

override fun onInputDeviceRemoved(deviceId: Int) {
    removeShipForID(deviceId)
}

private fun getShipForID(shipID: Int): Ship {
    return ships.get(shipID) ?: Ship().also {
        ships.append(shipID, it)
    }
}

private fun removeShipForID(shipID: Int) {
    ships.remove(shipID)
}

Java

private final SparseArray<Ship> ships = new SparseArray<Ship>();

@Override
public void onInputDeviceAdded(int deviceId) {
    getShipForID(deviceId);
}

@Override
public void onInputDeviceRemoved(int deviceId) {
    removeShipForID(deviceId);
}

private Ship getShipForID(int shipID) {
    Ship currentShip = ships.get(shipID);
    if ( null == currentShip ) {
        currentShip = new Ship();
        ships.append(shipID, currentShip);
    }
    return currentShip;
}

private void removeShipForID(int shipID) {
    ships.remove(shipID);
}

處理多個控制器輸入

您的遊戲應執行以下迴圈,處理來自多個控制器的輸入:

  1. 偵測是否發生輸入事件。
  2. 找出輸入來源及其裝置 ID。
  3. 根據輸入事件按鍵碼或軸值指定的動作,更新與該裝置 ID 相關聯的玩家顯示圖片。
  4. 轉譯及更新使用者介面。

KeyEventMotionEvent 輸入事件有相關聯的裝置 ID。您的遊戲可以利用此內容判斷輸入事件來自哪個控制器,並更新與該控制器相關聯的玩家顯示圖片。

下列程式碼片段顯示如何取得對應遊戲控制器裝置 ID 的玩家顯示圖片參照,並依據使用者的按鈕按下該控制器來更新遊戲。

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
        event.deviceId.takeIf { it != -1 }?.also { deviceId ->
            val currentShip: Ship = getShipForID(deviceId)
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            return true
        }
    }
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
        int deviceId = event.getDeviceId();
        if (deviceId != -1) {
            Ship currentShip = getShipForId(deviceId);
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            ...
            return true;
        }
    }
    return super.onKeyDown(keyCode, event);
}

注意: 最佳做法是,當使用者的遊戲控制器中斷連線時,您應暫停遊戲,並詢問使用者是否要重新連線。