Chrome-Dino-Spiel mit dem Gamepad spielen

Hier erfahren Sie, wie Sie die Gamepad API verwenden, um Ihre Webspiele auf die nächste Stufe zu heben.

Das Easter Egg auf der Offline-Seite von Chrome ist eines der schlechtest gehüteten Geheimnisse der Geschichte ([citation needed], aber die Behauptung wurde aus dramaturgischen Gründen aufgestellt). Wenn Sie die Leertaste drücken oder auf Mobilgeräten auf den Dinosaurier tippen, wird aus der Offlineseite ein spielbares Arcade-Spiel. Vielleicht wissen Sie bereits, dass Sie nicht offline gehen müssen, wenn Sie spielen möchten: In Chrome können Sie einfach zu about://dino navigieren oder, wenn Sie ein Technikfan sind, about://network-error/-106 aufrufen. Wusstest du aber, dass jeden Monat 270 Millionen Chrome-Dino-Spiele gespielt werden?

Die Offline-Seite von Chrome mit dem Chrome-Dino-Spiel.
Drücke die Leertaste, um das Spiel zu starten.

Ein weiterer Fakt, der wohl nützlicher ist und den du vielleicht nicht kennst, ist, dass du das Spiel im Arcade-Modus mit einem Gamepad spielen kannst. Die Gamepad-Unterstützung wurde vor etwa einem Jahr mit einem Commit von Reilly Grant hinzugefügt. Wie Sie sehen, ist das Spiel wie der Rest des Chromium-Projekts vollständig Open Source. In diesem Beitrag möchte ich Ihnen zeigen, wie Sie die Gamepad API verwenden.

Gamepad API verwenden

Funktionserkennung und Browserunterstützung

Die Gamepad API wird sowohl auf Computern als auch auf Mobilgeräten von allen gängigen Browsern unterstützt. Mit dem folgenden Snippet können Sie erkennen, ob die Gamepad API unterstützt wird:

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

So stellt der Browser ein Gamepad dar

Der Browser stellt Gamepads als Gamepad-Objekte dar. Ein Gamepad hat die folgenden Eigenschaften:

  • id: Ein Identifikationsstring für das Gamepad. Dieser String gibt die Marke oder den Stil des verbundenen Gamepad-Geräts an.
  • displayId: Die VRDisplay.displayId einer zugehörigen VRDisplay (falls relevant).
  • index: Der Index des Gamepads im Navigator.
  • connected: Gibt an, ob das Gamepad noch mit dem System verbunden ist.
  • hand: Ein Enum, das definiert, in welcher Hand der Controller gehalten wird oder höchstwahrscheinlich gehalten wird.
  • timestamp: Der Zeitpunkt der letzten Aktualisierung der Daten für dieses Gamepad.
  • mapping: Die für dieses Gerät verwendete Tasten- und Achsenzuordnung, entweder "standard" oder "xr-standard".
  • pose: Ein GamepadPose-Objekt, das die mit einem WebVR-Controller verknüpften Informationen zur Position darstellt.
  • axes: Ein Array von Werten für alle Achsen des Gamepads, die linear auf den Bereich von -1.01.0 normalisiert werden.
  • buttons: Ein Array von Schaltflächenstatus für alle Schaltflächen des Gamepads.

Schaltflächen können digital (gedrückt oder nicht gedrückt) oder analog (z. B. zu 78% gedrückt) sein. Aus diesem Grund werden Schaltflächen als GamepadButton-Objekte mit den folgenden Attributen gemeldet:

  • pressed: Der gedrückte Zustand der Schaltfläche (true, wenn die Schaltfläche gedrückt wird, und false, wenn sie nicht gedrückt wird).
  • touched: Der Berührungsstatus der Schaltfläche. Wenn die Schaltfläche Berührungen erkennen kann, ist diese Eigenschaft true, wenn die Schaltfläche berührt wird, und false, wenn dies nicht der Fall ist.
  • value: Bei Tasten mit einem analogen Sensor gibt diese Eigenschaft an, wie stark die Taste gedrückt wurde. Der Wert wird linear auf den Bereich von 0.0 bis 1.0 normalisiert.
  • hapticActuators: Ein Array mit GamepadHapticActuator-Objekten, die jeweils die auf dem Controller verfügbare Haptik-Hardware darstellen.

Je nach Browser und Gamepad kann es sein, dass Sie auf die vibrationActuator-Eigenschaft stoßen. Es sind zwei Arten von Rumble-Effekten möglich:

  • Dual-Rumble: Der haptische Feedbackeffekt, der von zwei Aktuatoren mit exzentrischer rotierender Masse erzeugt wird, einem in jedem Griff des Gamepads.
  • Trigger-Rumble: Der haptische Feedbackeffekt wird durch zwei unabhängige Motoren erzeugt, wobei sich jeweils ein Motor in jedem der Gamepad-Trigger befindet.

Die folgende schematische Übersicht, die direkt aus der Spezifikation stammt, zeigt die Zuordnung und Anordnung der Tasten und Achsen auf einem generischen Gamepad.

Schematische Übersicht über die Tasten- und Achsenzuordnungen eines gängigen Gamepads.
Visuelle Darstellung eines Standard-Gamepad-Layouts (Quelle).

Benachrichtigung, wenn ein Gamepad verbunden wird

Wenn Sie wissen möchten, wann ein Gamepad verbunden ist, achten Sie auf das gamepadconnected-Ereignis, das für das window-Objekt ausgelöst wird. Wenn der Nutzer ein Gamepad anschließt, was entweder über USB oder Bluetooth erfolgen kann, wird ein GamepadEvent ausgelöst, das die Details des Gamepads in einer entsprechend benannten gamepad-Eigenschaft enthält. Im Folgenden sehen Sie ein Beispiel für einen Xbox 360-Controller, den ich noch hatte (ja, ich bin ein Fan von Retro-Gaming).

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"}
  */
});

Benachrichtigung, wenn ein Controller getrennt wird

Benachrichtigungen über das Trennen von Gamepads erfolgen analog zur Erkennung von Verbindungen. Dieses Mal wartet die App auf das gamepaddisconnected-Ereignis. Im folgenden Beispiel ist zu sehen, wie connected jetzt false ist, wenn ich den Xbox 360-Controller trenne.

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

Das Gamepad in deinem Game-Loop

Um ein Gamepad zu erhalten, rufen Sie zuerst navigator.getGamepads() auf. Dadurch wird ein Array mit Gamepad-Elementen zurückgegeben. Das Array in Chrome hat immer eine feste Länge von vier Elementen. Wenn null oder weniger als vier Gamepads verbunden sind, kann ein Element einfach null sein. Prüfe immer alle Elemente des Arrays und beachte, dass Gamepads sich ihren Slot „merken“ und möglicherweise nicht immer im ersten verfügbaren Slot vorhanden sind.

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

Wenn ein oder mehrere Gamepads verbunden sind, aber navigator.getGamepads() weiterhin null Elemente meldet, musst du jedes Gamepad möglicherweise durch Drücken einer beliebigen Taste „aufwecken“. Anschließend können Sie die Gamepad-Zustände in Ihrer Spielschleife abrufen, wie im folgenden Code gezeigt.

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

Der Vibrationsaktuator

Die vibrationActuator-Property gibt ein GamepadHapticActuator-Objekt zurück, das einer Konfiguration von Motoren oder anderen Aktuatoren entspricht, die eine Kraft für haptisches Feedback ausüben können. Haptische Effekte können durch Aufrufen von Gamepad.vibrationActuator.playEffect() wiedergegeben werden. Die einzigen gültigen Effektarten sind 'dual-rumble' und 'trigger-rumble'.

Unterstützte Rütteleffekte

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.
}

Duales Rütteln

„Dual-Rumble“ beschreibt eine haptische Konfiguration mit einem exzentrischen rotierenden Massenvibrationsmotor in jedem Griff eines Standard-Gamepads. In dieser Konfiguration kann jeder Motor das gesamte Gamepad vibrieren. Die beiden Massen sind ungleich, sodass die Auswirkungen der einzelnen Massen kombiniert werden können, um komplexere haptische Effekte zu erzeugen. Dual-Rumble-Effekte werden durch vier Parameter definiert:

  • duration: Legt die Dauer des Vibrationseffekts in Millisekunden fest.
  • startDelay: Legt die Dauer der Verzögerung fest, bis die Vibration gestartet wird.
  • strongMagnitude und weakMagnitude: Legen Sie die Vibrationsintensitätsstufen für die schwereren und leichteren Motoren mit exzentrischer rotierender Masse fest, die auf den Bereich 0.01.0 normalisiert sind.
// 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,
  });
};

Vibrationen des Triggers auslösen

Das Trigger-Rumble ist ein haptischer Feedbackeffekt, der von zwei unabhängigen Motoren erzeugt wird. Eine befindet sich in jedem der Gamepad-Trigger.

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

Integration mit der Richtlinie für Berechtigungen

In der Gamepad API-Spezifikation wird ein richtlinienkonformes Feature definiert, das durch den String "gamepad" identifiziert wird. Der Standardwert von allowlist ist "self". Die Berechtigungsrichtlinie eines Dokuments bestimmt, ob Inhalte in diesem Dokument auf navigator.getGamepads() zugreifen dürfen. Wenn die Funktion in einem Dokument deaktiviert ist, darf in diesem Dokument kein Inhalt navigator.getGamepads() verwenden. Außerdem werden die Ereignisse gamepadconnected und gamepaddisconnected nicht ausgelöst.

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

Demo

Im folgenden Beispiel ist eine Demo für den Gamepad-Tester eingebettet. Der Quellcode ist auf Glitch verfügbar. Sie können die Demo ausprobieren, indem Sie ein Gamepad über USB oder Bluetooth verbinden und eine beliebige Taste drücken oder eine beliebige Achse bewegen.

Bonus: Chrome-Dino auf web.dev spielen

Sie können das Chrome-Dino-Spiel mit Ihrem Gamepad auf dieser Website spielen. Der Quellcode ist auf GitHub verfügbar. Sehen Sie sich die Implementierung des Gamepad-Polling in trex-runner.js an und beachten Sie, wie Tastendrücke emuliert werden.

Damit die Chrome-Dino-Gamepad-Demo funktioniert, habe ich das Chrome-Dino-Spiel aus dem Chromium-Kernprojekt herausgelöst (und dabei einen früheren Versuch von Arnelle Ballane aktualisiert), es auf einer eigenständigen Website platziert, die vorhandene Gamepad-API-Implementierung durch Hinzufügen von Ducking- und Vibrationseffekten erweitert und einen Vollbildmodus erstellt. Mehul Satardekar hat eine Darkmode-Implementierung beigesteuert. Viel Spaß beim Spielen!

Danksagungen

Dieses Dokument wurde von François Beaufort und Joe Medley geprüft. Die Gamepad API-Spezifikation wird von Steve Agoston, James Hollyer und Matt Reynolds bearbeitet. Die ehemaligen Spec-Editoren sind Brandon Jones, Scott Graham und Ted Mielczarek. Die Spezifikation für Gamepad-Erweiterungen wird von Brandon Jones bearbeitet. Hero-Image von Laura Torrent Puig.