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

Dowiedz się, jak korzystać z interfejsu Gamepad API, aby podnieść swoje gry internetowe na wyższy poziom.

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. Czy wiesz, że co miesiąc w grę z dinozaurem w Chrome gra 270 milionów użytkowników?

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

Inną przydatną informacją, o której możesz nie wiedzieć, jest to, że w trybie arkadowym możesz grać za pomocą kontrolera. Obsługa kontrolera została dodana około roku temu w commit autorstwa Reilly Grant. 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: enumeracja określająca, w której ręce trzymana jest kontrolka lub w której najprawdopodobniej jest trzymana.
  • 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 naciśnięcia przycisku (true, jeśli 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 dotykowy dostępny w 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.
  • Trigger-Rumble: efekt haptyczny generowany przez 2 niezależne silniki, z jednym silnikiem w każdym z gałek gamepada.

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 gamepad jest podłączony, nasłuchuj zdarzenia gamepadconnected, które uruchamia obiekt 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 właściwości gamepad. Poniżej możesz zobaczyć przykład kontrolera Xbox 360, który miałem pod ręką (tak, lubię retro gry).

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 elementami Gamepad. 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 elementów wykonawczych, które mogą wywierać siłę w celu zapewnienia informacji zwrotnej haptycznej. 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ójne wibracje to konfiguracja haptyczna z niesymetrycznym wibrującym silnikiem wibrującym w każdej rączce standardowego kontrolera. 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 wibracji podwójnych są zdefiniowane przez 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, znormalizowane 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,
  });
};

Włącz wibracje

Wibracje spustu to efekt haptyczny generowany przez 2 niezależne silniki, z których każdy znajduje się w spustach kontrolera.

// 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". Domyślna wartość parametru 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 jest wyłączona w jakimkolwiek dokumencie, żadna zawartość w tym dokumencie nie będzie mogła używać funkcji navigator.getGamepads(). Nie będą też wywoływane zdarzenia gamepadconnectedgamepaddisconnected.

<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 stronie web.dev

W tej witrynie możesz grać w dinozaura w Chrome za pomocą kontrolera. Kod źródłowy jest dostępny na GitHubie. Sprawdź implementację pollingu kontrolera w trex-runner.js i zwróć uwagę, jak emuluje ona naciskanie 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 (poprawiając wcześniejsze rozwiązanie Arnelle Ballane), umieściłem ją na osobnej stronie, rozszerzyłem istniejące API sterowania, dodając efekty wyciszania i wibracji, utworzyłem tryb pełnoekranowy, a Mehul Satardekar opracował implementację trybu ciemnego. Miłego grania!

Podziękowania

Ten dokument został sprawdzony przez Françoisa BeaufortaJoe Medleya. Specyfikację interfejsu Gamepad API redagują Steve Agoston, James HollyerMatt Reynolds. Byli to: Brandon Jones, Scott Graham i Ted Mielczarek. Specyfikację rozszerzeń dla kontrolerów do gier redaguje Brandon Jones. Obraz główny autorstwa Laury Torrent Puig.