支持多个游戏控制器

虽然大多数游戏都设计为每个 Android 设备支持单个用户,但也可以支持在同一 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);
}

注意: 作为最佳实践,当用户的游戏控制器断开连接时,您应该暂停游戏并询问用户是否要重新连接。