Chrome-Dino-Spiel mit dem Gamepad spielen

Hier erfahren Sie, wie Sie Ihre Webspiele mithilfe der Gamepad API optimieren können.

Das Easter Egg der Offlineseite von Chrome ist eines der am schlimmsten gehüteten Geheimnisse der Geschichte ([citation needed], aber Behauptung wegen des dramatischen Effekts). Wenn Sie die Leertaste drücken oder auf Mobilgeräten auf den Dinosaurier tippen, wird die Offlineseite zu einem spielbaren Arcade-Spiel. Vielleicht ist Ihnen bewusst, dass Sie zum Spielen nicht offline gehen müssen: In Chrome können Sie einfach zu about://dino gehen oder, im Namen eines Geeks, zu about://network-error/-106 gehen. Aber wussten Sie auch, dass jeden Monat 270 Millionen Chrome-Dino-Spiele gespielt werden?

Die Offlineseite von Chrome mit dem Chrome-Dino-Spiel.
Drücke zum Spielen die Leertaste.

Eine weitere Tatsache, die wahrscheinlich nützlicher zu wissen ist, ist die Tatsache, dass Sie im Arcade-Modus das Spiel mit einem Gamepad spielen können. Die Gamepad-Unterstützung wurde zum Zeitpunkt der Erstellung dieses Dokuments vor etwa einem Jahr in 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 die Verwendung der Gamepad API zeigen.

Gamepad API verwenden

Funktionserkennung und Browserunterstützung

Die Gamepad API bietet eine universelle Browserunterstützung sowohl auf Computern als auch auf Mobilgeräten. Mit dem folgenden Snippet können Sie feststellen, 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 Identifizierungsstring für das Gamepad. Dieser String identifiziert die Marke oder den Stil des verbundenen Gamepad-Geräts.
  • displayId: VRDisplay.displayId einer verknüpften 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-Wert, der definiert, in welcher Hand der Controller am ehesten gehalten wird.
  • timestamp: Das letzte Mal, dass die Daten für dieses Gamepad aktualisiert wurden.
  • mapping: Die Schaltflächen- und Achsenzuordnung, die für dieses Gerät verwendet wird, entweder "standard" oder "xr-standard".
  • pose: Ein GamepadPose-Objekt, das die Poseninformationen darstellt, die mit einem WebVR-Controller verknüpft sind.
  • axes: Ein Array von Werten für alle Achsen des Gamepads, linear auf den Bereich von -1.01.0 normalisiert.
  • buttons: Ein Array mit Tastenstatus für alle Tasten des Gamepads.

Beachte, dass die Schaltflächen digital (drückt oder nicht gedrückt) oder analog (z. B. 78% gedrückt werden) sein können. 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ührte Zustand der Schaltfläche. Wenn die Schaltfläche Berührungen erkennen kann, ist diese Eigenschaft true, wenn sie berührt wird, und andernfalls false.
  • value: Bei Schaltflächen mit analogem Sensor stellt diese Eigenschaft den Umfang dar, in dem die Schaltfläche gedrückt wurde, linear auf den Bereich von 0.01.0 normalisiert.
  • hapticActuators: Ein Array mit GamepadHapticActuator-Objekten, von denen jedes die Hardware für haptisches Feedback darstellt, die auf dem Controller verfügbar ist.

Je nach Browser und Gamepad sehen Sie außerdem möglicherweise ein vibrationActuator-Attribut. Dies ermöglicht zwei Arten von Rumble-Effekten:

  • Dual-Rumble: Der haptische Feedbackeffekt, der von zwei exzentrischen rotierenden Massenantrieben erzeugt wird, jeweils einer in jedem Griff des Gamepads.
  • Trigger-Rumble: Der haptische Feedback-Effekt, der von zwei unabhängigen Motoren erzeugt wird, wobei sich ein Motor in jedem der Trigger des Gamepads befindet.

Die folgende schematische Übersicht direkt aus der Spezifikation abgeleitet zeigt die Zuordnung und die Anordnung der Schaltflächen und Achsen auf einem generischen Gamepad.

Schematische Übersicht der Schaltflächen- und Achsenzuordnungen eines gängigen Gamepads.
Visuelle Darstellung eines standardmäßigen Gamepad-Layouts (Quelle).

Benachrichtigung, wenn ein Gamepad verbunden wird

Wenn Sie wissen möchten, wann ein Gamepad verbunden ist, warten Sie auf das gamepadconnected-Ereignis, das beim window-Objekt ausgelöst wird. Wenn der Nutzer ein Gamepad anschließt, was entweder über USB oder Bluetooth möglich ist, wird ein GamepadEvent ausgelöst, der die Details des Gamepads in einem passend benannten gamepad-Attribut enthält. Im folgenden Beispiel siehst du ein Beispiel für einen Xbox 360-Controller, den ich herumliegen hatte (ja, ich interessiere mich für 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 die Verbindung zu einem Gamepad getrennt wird

Die Benachrichtigung über Gamepad-Unterbrechungen erfolgt analog zur Art und Weise, wie Verbindungen erkannt werden. Dieses Mal wartet die App auf das Ereignis gamepaddisconnected. Beachten Sie, dass connected im folgenden Beispiel jetzt false ist, wenn ich den Xbox 360-Controller vom 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 der Spielschleife

Das Abrufen eines Gamepads beginnt mit einem Aufruf von navigator.getGamepads(), wodurch ein Array mit Gamepad-Elementen zurückgegeben wird. 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 auch nur null sein. Prüfe immer alle Elemente des Arrays. Gamepads „merken“ sich ihren Slot und befinden sich möglicherweise nicht immer am ersten verfügbaren Slot.

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

Wenn ein oder mehrere Gamepads verbunden sind, navigator.getGamepads() aber weiterhin null Elemente meldet, müssen Sie möglicherweise jedes Gamepad durch Drücken einer der Tasten aktivieren. Anschließend können Sie die Gamepad-Status in Ihrer Spielschleife abfragen, 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 Vibrationsauslöser

Das Attribut vibrationActuator gibt ein GamepadHapticActuator-Objekt zurück, das einer Konfiguration von Motoren oder anderen Bedienelementen entspricht, die eine Kraft für haptisches Feedback anwenden können. Haptische Effekte können durch Aufrufen von Gamepad.vibrationActuator.playEffect() wiedergegeben werden. Der einzige gültige Effekttyp ist 'dual-rumble'. Dual-Rumble beschreibt eine haptische Konfiguration mit einem exzentrischen rotierenden Massenvibrationsmotor in jedem Handle eines Standard-Gamepads. Bei dieser Konfiguration kann jeder Motor das gesamte Gamepad vibrieren. Die beiden Massen sind ungleich, sodass ihre Auswirkungen 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ät für die schwereren und leichteren exzentrischen rotierenden Massenmotoren fest, normalisiert auf den Bereich 0.01.0.

Unterstützte Rumpeleffekte

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

Dual-Rumble

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

Trigger-Ruckeln

// 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 Berechtigungsrichtlinie

Die Gamepad API-Spezifikation definiert eine richtliniengesteuerte Funktion, die durch den String "gamepad" identifiziert wird. Die Standardeinstellung allowlist ist "self". Die Berechtigungsrichtlinie eines Dokuments bestimmt, ob Inhalte in diesem Dokument auf navigator.getGamepads() zugreifen dürfen. Wenn diese Option in einem Dokument deaktiviert ist, darf kein Inhalt im Dokument navigator.getGamepads() verwenden und die Ereignisse gamepadconnected und gamepaddisconnected werden nicht ausgelöst.

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

Demo

Im folgenden Beispiel ist eine Gamepad-Tester-Demo eingebettet. Der Quellcode ist auf Glitch verfügbar. Probieren Sie die Demo aus, indem Sie ein Gamepad über USB oder Bluetooth verbinden und eine der Tasten drücken oder eine seiner Achsen bewegen.

Bonus: Chrome Dino auf web.dev spielen

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

Damit die Chrome Dino-Gamepad-Demo funktioniert, habe ich das Chrome-Dino-Spiel aus dem Chromium-Kernprojekt entfernt (eine frühere Version von Arnelle Ballane aktualisiert), es auf einer eigenständigen Website platziert, die vorhandene Gamepad API-Implementierung um Ducking- und Vibrationseffekte erweitert, einen Vollbildmodus erstellt und Mehul Satardekar einen dunklen Modus beigetragen. 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 Spezifikationsbearbeiter 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.