複数のゲーム コントローラをサポートする

ほとんどのゲームは 1 台の Android デバイスに対して 1 人のユーザーをサポートするように設計されていますが、1 台の Android デバイスに複数のゲーム コントローラを同時に接続することによって複数のユーザーをサポートすることも可能です。

このレッスンでは、1 台のデバイス上でのマルチプレーヤー型ゲームにおいて、接続されている複数のコントローラからの入力を処理するための基本的な手法について説明します。また、プレーヤーのアバターと各コントローラ デバイス間のマッピングを管理する方法と、コントローラの入力イベントを適切に処理する方法についても説明します。

プレーヤーをコントローラ デバイス 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 を利用して、入力イベントの発生元のコントローラを特定し、そのコントローラに関連付けられているプレーヤーのアバターを更新します。

次のコード スニペットは、ゲーム コントローラのデバイス 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);
    }
    

注: ユーザーのゲーム コントローラが取り外された場合、ゲームを一時停止して、再接続するかどうかを尋ねることをおすすめします。