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

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

Wielkanocna niespodzianka dostępna w Chrome jest jednym z najbardziej skrywanych sekretów w historii ([citation needed]), choć deklaruje się, że mają ogromny efekt. Jeśli naciśniesz spację lub klikniesz dinozaura na urządzeniu mobilnym, strona offline stanie się grą zręcznościową. Być może wiesz, że nie musisz przełączać się do trybu offline, gdy chcesz grać. W Chrome wystarczy po prostu przejść do about://dino, a jeśli jesteś amatorem – otwórz about://network-error/-106. Ale czy wiesz, że każdego miesiąca w Chrome gra 270 milionów gier z dinozaurami?

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

Inną przydatniejszą przydatną cechą, o której możesz nie wiedzieć, jest to, że w trybie zręcznościowym możesz grać w grę za pomocą pada do gier. Obsługa pada do gier została dodana mniej więcej rok temu w momencie napisania tego zobowiązania przez Reilly'ego Granta. Jak widać, gra, tak jak reszta projektu Chromium, jest w pełni oprogramowana na licencji open source. W tym poście pokażę, jak korzystać z interfejsu Gamepad API.

Użyj interfejsu Gamepad API

Wykrywanie funkcji i obsługa przeglądarek

Interfejs Gamepad API zapewnia doskonałą obsługę przeglądarek zarówno na komputerach, jak i urządzeniach mobilnych. Aby sprawdzić, czy interfejs Gamepad API jest obsługiwany, skorzystaj z tego fragmentu kodu:

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

Jak przeglądarka reprezentuje pad do gier

Przeglądarka reprezentuje pady do gier jako obiekty Gamepad. Element Gamepad ma te właściwości:

  • id: ciąg identyfikujący pada do gier. Ten ciąg znaków identyfikuje markę lub styl połączonego pada do gier.
  • displayId: VRDisplay.displayId powiązanego elementu VRDisplay (w odpowiednich przypadkach).
  • index: indeks pada do gier w nawigatorze.
  • connected: wskazuje, czy pad do gier jest nadal połączony z systemem.
  • hand: wyliczenie określające, w jakiej ręce trzyma kontroler lub najprawdopodobniej się przytrzyma.
  • timestamp: data ostatniej aktualizacji danych pada do gier.
  • mapping: mapowanie przycisku i osi używane przez to urządzenie. Może to być "standard" lub "xr-standard".
  • pose: obiekt GamepadPose reprezentujący informacje o pozycji powiązane z kontrolerem WebVR.
  • axes: tablica wartości wszystkich osi pada do gier, znormalizowana liniowo do zakresu -1.01.0.
  • buttons: tablica stanów przycisków wszystkich przycisków pada do gier.

Pamiętaj, że mogą to być przyciski cyfrowe (naciśnięte lub nie) lub analogowe (na przykład 78% naciśniętego). Właśnie dlatego przyciski są raportowane jako obiekty GamepadButton z tymi atrybutami:

  • pressed: stan naciśnięcia przycisku (true, jeśli przycisk jest naciśnięty, i false, jeśli nie został naciśnięty.
  • touched: stan dotknięcia przycisku. Jeśli przycisk może wykrywać dotyk, ta właściwość ma wartość true, jeśli przycisk jest klikany, lub wartość false w innym przypadku.
  • value: w przypadku przycisków z czujnikiem analogowym ta właściwość określa wartość naciśnięcia przycisku, znormalizowaną liniowo do zakresu 0.01.0.
  • hapticActuators: tablica zawierająca obiekty GamepadHapticActuator, z których każdy reprezentuje sprzęt sterujący dotykowo dostępny w kontrole.

Dodatkową rzeczą, która może się pojawić w zależności od używanej przeglądarki i pada do gier, jest właściwość vibrationActuator. Można wybrać 2 rodzaje efektów drżenia:

  • Podwójna obrotowa reakcja: efekt reakcji na dotyk generowany przez 2 niezwykłe ruchome ruchy obrotowe, po jednym w każdym uchwycie pada do gier.
  • Spust spustowy: efekt reakcji haptycznej generowany przez 2 niezależne silniki, w tym po jednym w każdym z elementów uruchamiających pada.

Poniższa instrukcja, wykonana bezpośrednio ze specyfikacji, przedstawia mapowanie oraz układ przycisków i osi na ogólnym padzie do gier.

Schematyczne omówienie mapowania przycisku i osi popularnego pada do gier.
Wizualne przedstawienie standardowego układu pada do gier (źródło).

Powiadomienie o podłączeniu pada do gier

Aby dowiedzieć się, kiedy pad do gier jest podłączony, nasłuchuj zdarzenia gamepadconnected wywoływanego w obiekcie window. Gdy użytkownik podłącza pad do gier (przez USB lub Bluetooth), uruchamiany jest GamepadEvent ze szczegółami pada do gier we właściwej nazwie gamepad. Poniżej znajdziesz przykład kontrolera Xbox 360, z którego wcześniej korzystałem (tak, lubię gry 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 pada do gier

Powiadomienie o odłączeniu pada do gier odbywa się podobnie jak w przypadku wykrycia połączeń. Tym razem aplikacja nasłuchuje zdarzenia gamepaddisconnected. Zwróć uwagę, że w przykładzie poniżej connected po odłączeniu kontrolera Xbox 360 wynosi teraz false.

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 do gier w pętli gry

Zarejestrowanie pada do gier rozpoczyna się od wywołania funkcji navigator.getGamepads(), które zwraca tablicę z Gamepad elementami. Tablica w Chrome zawsze ma stałą długość 4 elementów. Jeśli podłączonych jest nie więcej niż 4 padów do gier, element może mieć po prostu null. Pamiętaj, aby zawsze sprawdzać wszystkie elementy tablicy i pamiętaj, że pady do gier „zapamiętują” swoje gniazdo i nie zawsze wyświetlają się na pierwszym dostępnym miejscu.

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

Jeśli jest połączony co najmniej 1 pad do gier, ale navigator.getGamepads() nadal zgłasza null elementów, być może trzeba będzie „wybudzić” każdy pad do gier, naciskając dowolny z jego przycisków. Następnie możesz przeprowadzić sondowanie stanu pada do gier w pętli gry, jak pokazano w tym 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();

Wyzwalacz wibracji

Właściwość vibrationActuator zwraca obiekt GamepadHapticActuator, który odpowiada konfiguracji silników lub innych elementów uruchamiających, które mogą stosować siłę na potrzeby reagowania na dotyk. Efekty haptyczne można odtwarzać, wywołując Gamepad.vibrationActuator.playEffect(). Jedyny prawidłowy typ efektu to 'dual-rumble'. Podwójny rumień to konfiguracja haptyczna z ekscentrycznymi obrotowymi silnikami wibracyjnymi w każdym uchwytie standardowego pada do gier. W takiej konfiguracji każdy z silników może wibrować cały pad do gier. Te 2 masy są nierówne, więc efekty każdego z nich można łączyć, aby uzyskać bardziej złożone efekty haptyczne. Efekty podwójne są definiowane za pomocą 4 parametrów:

  • duration: ustawia czas trwania efektu wibracji w milisekundach.
  • startDelay: ustawia czas opóźnienia do momentu, gdy wibracja zostanie uruchomiona.
  • strongMagnitude i weakMagnitude: ustaw poziomy natężenia wibracji dla cięższych i mniejszych mimośrodkowych silników z masą obrotową znormalizowanymi w zakresie 0.01.0.

Obsługiwane efekty drżenia

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ójne drżenie

// 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 brzęczenie

// 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 zasadą dotyczącą uprawnień

Specyfikacja interfejsu Gamepad API definiuje funkcję kontrolowaną przez zasady identyfikowaną przez ciąg znaków "gamepad". Wartością domyślną w polu allowlist jest "self". Zasada uprawnień dokumentu określa, czy jakakolwiek treść tego dokumentu może uzyskać dostęp do elementu navigator.getGamepads(). Jeśli zasada zostanie wyłączona w dowolnym dokumencie, żadna treść dokumentu nie będzie mogła używać elementu navigator.getGamepads() ani nie będą uruchamiane zdarzenia gamepadconnected i gamepaddisconnected.

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

Pokaz

W tym przykładzie umieszczono prezentację testera pada do gier. Kod źródłowy jest dostępny w Glitch. Wypróbuj wersję demonstracyjną: podłącz pad do gier przez USB lub Bluetooth i naciśnij dowolny przycisk lub dowolną oś.

Bonus: zagraj w dinozaura z Chrome na web.dev

Na tej stronie możesz zagrać w dinozaura w Chrome na swoim padzie do gier. Kod źródłowy jest dostępny na GitHubie. Zapoznaj się z implementacją odpytywania pada do gier w trex-runner.js i zobacz, jak emuluje ona naciśnięcia klawiszy.

Aby umożliwić działanie wersji demonstracyjnej pada do gier z dinozaurem w Chrome, stworzyłem grę z dinozaurem w podstawowym projekcie Chromium (aktualizując wcześniejszą wersję autorstwa Arnelle Ballane), umieściłem ją w samodzielnej witrynie, rozszerzyliśmy implementację interfejsu API do gier, dodając efekty wyciszania i wibracji, utworzyłem tryb pełnoekranowy i wdrożył tryb ciemny Mehul Satardekar. Miłego grania!

Podziękowania

Ten dokument sprawdzili François Beaufort i Joe Medley. Specyfikację interfejsu Gamepad API edytują Steve Agoston, James Hollyer i Matt Reynolds. Poprzedni redaktorzy specyfikacji to Brandon Jones, Scott Graham i Ted Mielczarek. Specyfikację rozszerzeń gamepadów edytuje Brandon Jones. Baner powitalny autorstwa Laury Torrent Puig.