支持多个游戏控制器

虽然大多数游戏设计为每台 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);
    }
    

注意:最佳做法是,当用户的游戏控制器断开连接时,暂停游戏并询问用户是否想要重新连接。