Graj w grę z dinozaurem w Chrome na padzie do gier

Dowiedz się, jak korzystać z interfejsu Gamepad API, aby podnieść jakość swoich gier internetowych.

Ukryty żart na stronie offline w Chrome jest jedną z najgorzej strzeżonych tajemnic w historii ([citation needed]), ale to stwierdzenie zostało użyte dla podkreślenia dramatyzmu. Jeśli naciśniesz klawisz spacji lub na urządzeniach mobilnych klikniesz dinozaura, strona offline zmieni się w grę zręcznościową. Warto pamiętać, że nie trzeba przechodzić w tryb offline, aby zagrać: w Chrome możesz po prostu przejść do about://dino lub, jeśli jesteś geekiem, do about://network-error/-106. Ale czy wiesz, że co miesiąc w Chrome 270 milionów gier z dinozaurami w Chrome gra?

Strona offline w Chrome z grą z dinozaurem
Aby zagrać, naciśnij spację.

Inną przydatną informacją, o której być może nie wiesz, jest to, że w trybie arkadowym możesz grać za pomocą kontrolera. Obsługę padów do gier dodano mniej więcej rok temu, kiedy to piszę, w commit przez Reilly Granta. Jak widać, gra, podobnie jak reszta projektu Chromium, jest w pełni otwartym kodem źródłowym. W tym poście pokażę, jak korzystać z interfejsu Gamepad API.

Korzystanie z interfejsu Gamepad API

Wykrywanie funkcji i obsługa przeglądarek

Interfejs Gamepad API jest świetnie obsługiwany przez przeglądarki na komputerach i urządzeniach mobilnych. Aby sprawdzić, czy interfejs Gamepad API jest obsługiwany, użyj tego fragmentu kodu:

if ('getGamepads' in navigator) {
  // The API is supported!
}

Jak przeglądarka przedstawia kontroler

Przeglądarka przedstawia kontrolery jako obiekty Gamepad. Element Gamepad ma te właściwości:

  • id: identyfikator kontrolera. Ten ciąg identyfikuje markę lub styl podłączonego kontrolera.
  • displayId:VRDisplay.displayId powiązanego konta VRDisplay (jeśli dotyczy).
  • index: indeks kontrolera w przewodniku.
  • connected: wskazuje, czy gamepad jest nadal połączony z systemem.
  • hand: typ definiujący, w której ręce trzymany jest kontroler lub w której najprawdopodobniej jest trzymany.
  • timestamp: ostatni czas aktualizacji danych dotyczących tego kontrolera.
  • mapping: mapowanie przycisków i osi używane na tym urządzeniu: "standard" lub "xr-standard".
  • pose: obiekt GamepadPose reprezentujący informacje o położeniu ciała powiązane z kontrolerem WebVR.
  • axes: tablica wartości dla wszystkich osi kontrolera, liniowo znormalizowana do zakresu -1.01.0.
  • buttons: tablica stanów wszystkich przycisków pada.

Pamiętaj, że przyciski mogą być cyfrowe (wciśnięte lub nie wciśnięte) lub analogowe (np. wciśnięte w 78%). Dlatego przyciski są raportowane jako obiekty GamepadButton z tymi atrybutami:

  • pressed: stan kliknięcia przycisku (true, jeśli przycisk jest naciśnięty, i false, jeśli nie jest naciśnięty.
  • touched: stan przycisku po dotknięciu. Jeśli przycisk może wykrywać dotyk, ta właściwość ma wartość true, gdy jest dotykany, i false w innym przypadku.
  • value: w przypadku przycisków z czujnikiem analogowym ta właściwość reprezentuje wartość, o jaką został naciśnięty przycisk, liniowo znormalizowana w zakresie 0.01.0.
  • hapticActuators: tablica zawierająca obiekty GamepadHapticActuator, z których każdy reprezentuje sprzęt do reakcji haptycznej dostępny na kontrolerze.

W zależności od przeglądarki i urządzenia sterującego możesz też natknąć się na właściwość vibrationActuator. Umożliwia 2 rodzaje efektów wibracji:

  • Dual-Rumble: efekt haptyczny generowany przez dwa mimośrodowe elementy wykonawcze z rotacyjną masą, po jednym w każdym uchwycie kontrolera.
  • Wiatrówka: efekt reakcji haptycznych generowany przez 2 niezależne silniki, z którym jeden jest umieszczony w każdym z spustów pada do gier.

Ten schemat, który pochodzi bezpośrednio ze specyfikacji, przedstawia mapowanie i rozmieszczenie przycisków i osi na typowym padzie.

Schematyczny przegląd mapowania przycisków i osi w zwykłym kontrolerze.
Wizualizacja standardowego układu pada do gier (źródło).

Powiadomienie o podłączeniu kontrolera do gier

Aby dowiedzieć się, kiedy pad do gier jest podłączony, nasłuchuj zdarzenia gamepadconnected, które jest aktywowane w obiekcie window. Gdy użytkownik podłączy kontroler, co może nastąpić przez USB lub Bluetooth, zostanie wywołana GamepadEvent, która zawiera szczegóły kontrolera w odpowiedniej nazwie właściwości gamepad. Poniżej widać przykład kontrolera Xbox 360, który leży mi pod ręką (tak, uwielbiam grać retro).

window.addEventListener('gamepadconnected', (event) => {
  console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: true
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
  */
});

Powiadomienie o odłączeniu kontrolera

Powiadomienia o odłączeniu kontrolera działają analogicznie do sposobu wykrywania połączeń. Tym razem aplikacja nasłuchuje zdarzenia gamepaddisconnected. Zwróć uwagę, że w tym przykładzie connected jest teraz false, gdy odłączę kontroler Xbox 360.

window.addEventListener('gamepaddisconnected', (event) => {
  console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: false
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: null
  */
});

Pad w pętli gry

Pobieranie danych z kontrolera zaczyna się od wywołania funkcji navigator.getGamepads(), która zwraca tablicę z Gamepad elementami. Tablica w Chrome zawsze ma stałą długość 4 elementów. Jeśli nie ma żadnych lub mniej niż 4 podłączonych kontrolerów, element może być tylko null. Zawsze sprawdzaj wszystkie elementy tablicy. Pamiętaj, że kontrolery „zapamiętują” swoje miejsce i nie zawsze mogą być obecne w pierwszym dostępnym miejscu.

// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]

Jeśli masz podłączone 1 lub więcej kontrolerów, ale navigator.getGamepads() nadal raportuje null elementy, może być konieczne „obudzenie” każdego z nich przez naciśnięcie dowolnego przycisku. Następnie możesz odczytywać stany kontrolera w pętli gry, jak pokazano w poniższym kodzie.

const pollGamepads = () => {
  // Always call `navigator.getGamepads()` inside of
  // the game loop, not outside.
  const gamepads = navigator.getGamepads();
  for (const gamepad of gamepads) {
    // Disregard empty slots.
    if (!gamepad) {
      continue;
    }
    // Process the gamepad state.
    console.log(gamepad);
  }
  // Call yourself upon the next animation frame.
  // (Typically this happens every 60 times per second.)
  window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();

Silnik wibracyjny

Właściwość vibrationActuator zwraca obiekt GamepadHapticActuator, który odpowiada konfiguracji silników lub innych siłowników, które mogą wywierać siłę na potrzeby sprzężenia zwrotnego haptycznego. Efekty haptyczne można odtwarzać, wywołując Gamepad.vibrationActuator.playEffect(). Jedynymi prawidłowymi typami efektów są 'dual-rumble' i 'trigger-rumble'.

Obsługiwane efekty wibracji

if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
  // Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
  // Dual rumble supported.
} else {
  // Rumble effects aren't supported.
}

Podwójny wibrator

Podwójny ruch opisuje konfigurację haptyczną z niezwykłym, obracającym się silnikiem wibracyjnym w każdym uchuku standardowego pada do gier. W tej konfiguracji oba silniki mogą wibrować całym kontrolerem. Obie masy są różne, więc można łączyć efekty, które one generują, aby tworzyć bardziej złożone efekty haptyczne. Efekty podwójnych uderzeń określają 4 parametry:

  • duration: ustawia czas trwania efektu wibracji w milisekundach.
  • startDelay: określa czas opóźnienia przed rozpoczęciem wibracji.
  • strongMagnitudeweakMagnitude: ustaw poziomy intensywności wibracji dla cięższych i lżejszych silników z niesymetryczną masą wirnika, znormalizowanych do zakresu 0.01.0.
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  gamepad.vibrationActuator.playEffect('dual-rumble', {
    // Start delay in ms.
    startDelay: delay,
    // Duration in ms.
    duration: duration,
    // The magnitude of the weak actuator (between 0 and 1).
    weakMagnitude: weak,
    // The magnitude of the strong actuator (between 0 and 1).
    strongMagnitude: strong,
  });
};

Uderzenie spustu

Wibracja to efekt reakcji haptycznej generowany przez 2 niezależne silniki, z których jeden znajduje się w każdym ze spustów pada do gier.

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  // Feature detection.
  if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
    return;
  }
  gamepad.vibrationActuator.playEffect('trigger-rumble', {
    // Duration in ms.
    duration: duration,
    // The left trigger (between 0 and 1).
    leftTrigger: leftTrigger,
    // The right trigger (between 0 and 1).
    rightTrigger: rightTrigger,
  });
};

Integracja z zasadami dotyczącymi uprawnień

Specyfikacja interfejsu Gamepad API definiuje funkcję kontrolowaną przez zasady, którą można zidentyfikować za pomocą ciągu znaków "gamepad". Jej domyślna wartość allowlist to "self". Zasady dotyczące uprawnień dokumentu określają, czy jakiekolwiek treści w tym dokumencie mogą uzyskać dostęp do navigator.getGamepads(). Jeśli ta opcja zostanie wyłączona w dowolnym dokumencie, żadna treść dokumentu nie będzie mogła używać polecenia navigator.getGamepads() ani nie będą uruchamiane zdarzenia gamepadconnected i gamepaddisconnected.

<iframe src="index.html" allow="gamepad"></iframe>

Prezentacja

W tym przykładzie znajduje się demo testera kontrolera. Kod źródłowy jest dostępny na Glitch. Wypróbuj wersję demonstracyjną, podłączając kontroler przez USB lub Bluetooth i naciskając dowolny przycisk lub przesuwając dowolną oś.

Bonus: zagraj w grę z dinozaurem w Chrome na web.dev

W tej witrynie możesz grać w dinozaura w Chrome za pomocą kontrolera. Kod źródłowy jest dostępny na GitHubie. Zapoznaj się z implementacją odpytywania pada do gier w trex-runner.js i zwróć uwagę, jak emuluje ona naciśnięcia klawiszy.

Aby demo sterowania za pomocą kontrolera w grze Dinozaur w Chrome działało, wyodrębniłem grę Dinozaur w Chrome z głównego projektu Chromium (aktualizując wcześniejsze rozwiązanie Arnelle Ballane), umieściłem ją na osobnej stronie, rozszerzyłem istniejące API sterowania, dodając efekty wyciszenia i wibracji, utworzyłem tryb pełnoekranowy, a Mehul Satardekar opracował implementację trybu ciemnego. Miłego grania!

Podziękowania

Ten dokument zweryfikowali François Beaufort i Joe Medley. Specyfikację interfejsu Gamepad API edytują Steve Agoston, James Hollyer i Matt Reynolds. Byli to: Brandon Jones, Scott Graham i Ted Mielczarek. Specyfikację rozszerzeń dla kontrolerów do gier edytuje Brandon Jones. Obraz główny autorstwa Laury Torrent Puig.