Obsługa kilku kontrolerów do gier

Większość gier została zaprojektowana tak, aby obsługiwać jednego użytkownika na każde urządzenie z Androidem, ale można też zapewnić obsługę wielu użytkowników za pomocą kontrolerów do gier podłączonych jednocześnie na tym samym urządzeniu z Androidem.

W tej lekcji omówimy kilka podstawowych technik obsługi danych wejściowych w grze wieloosobowej na 1 urządzeniu za pomocą wielu połączonych kontrolerów. Obejmuje to utrzymywanie mapowania między awatarami graczy i każdym urządzeniem kontrolera oraz odpowiednie przetwarzanie zdarzeń wejściowych kontrolera.

Zmapuj graczy na identyfikatory urządzeń kontrolera

Gdy kontroler gier jest podłączony do urządzenia z Androidem, system przypisuje mu liczbę całkowitą. Identyfikatory połączonych kontrolerów gier możesz uzyskać, wywołując InputDevice.getDeviceIds(), jak pokazano w sekcji Sprawdzanie, czy kontroler gier jest podłączony. Następnie możesz powiązać każdy identyfikator urządzenia z graczem w grze i przetwarzać działania każdego z nich osobno.

Uwaga: na urządzeniach z Androidem 4.1 (poziom interfejsu API 16) lub nowszym możesz uzyskać deskryptor urządzenia wejściowego za pomocą funkcji getDescriptor(), która zwraca unikalną, trwałą wartość ciągu znaków dla urządzenia wejściowego. W przeciwieństwie do identyfikatora urządzenia wartość deskryptora nie zmienia się nawet wtedy, gdy urządzenie wejściowe zostanie odłączone, podłączone ponownie lub skonfigurowane ponownie.

Fragment kodu poniżej pokazuje, jak za pomocą SparseArray powiązać awatara gracza z konkretnym kontrolerem. W tym przykładzie zmienna mShips przechowuje zbiór obiektów Ship. Nowy awatar gracza jest tworzony w grze, gdy użytkownik podłączy nowy kontroler, i usuwany po usunięciu powiązanego z nim kontrolera.

Metody wywołań zwrotnych onInputDeviceAdded() i onInputDeviceRemoved() wchodzą w skład warstwy abstrakcji wprowadzonej w artykule Obsługa kontrolerów w różnych wersjach Androida. Dzięki zaimplementowaniu tych wywołań zwrotnych nasłuchujących gra może identyfikować identyfikator urządzenia kontrolera gier po dodaniu lub usunięciu kontrolera. Wykrywanie jest zgodne z Androidem 2.3 (poziom interfejsu API 9) i nowszym.

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);
}

Przetwarzaj dane wejściowe wielu kontrolerów

Gra powinna wykonać tę pętlę, aby przetwarzać dane wejściowe z kilku kontrolerów:

  1. Wykrywanie, czy wystąpiło zdarzenie wejściowe.
  2. Określ źródło danych wejściowych i identyfikator urządzenia.
  3. Na podstawie działania wskazywanego przez wpisany kod klucza zdarzenia lub wartość osi zaktualizuj awatar gracza powiązany z tym identyfikatorem urządzenia.
  4. Zrenderować i zaktualizować interfejs użytkownika.

Zdarzenia wejściowe KeyEvent i MotionEvent mają powiązane identyfikatory urządzeń. Gra może to wykorzystać, aby określić, z którego kontrolera pochodzi zdarzenie wejściowe, i zaktualizować awatara gracza powiązanego z tym kontrolerem.

Poniższy fragment kodu pokazuje, jak uzyskać odniesienie do awatara gracza odpowiadającego identyfikatorowi urządzenia kontrolera do gier, aby zaktualizować grę odpowiednio do naciśnięcia przycisku na tym kontrolerze.

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);
}

Uwaga: zgodnie ze sprawdzoną metodą, gdy kontroler gry użytkownika się rozłącza, wstrzymaj grę i zapytaj, czy użytkownik chce się połączyć jeszcze raz.