Gioca a Dino di Chrome con il tuo gamepad

Scopri come utilizzare l'API Gamepad per far fare un salto di qualità ai tuoi giochi web.

L'easter egg della pagina offline di Chrome è uno dei segreti peggio custoditi della storia ([citation needed], ma affermazione fatta per effetto drammatico). Se premi il tasto Barra spaziatrice o, su dispositivi mobili, tocchi il dinosauro, la pagina offline diventa un gioco arcade giocabile. Probabilmente sai che non devi necessariamente andare offline quando vuoi giocare: in Chrome, puoi semplicemente andare su about://dino o, se sei un appassionato di tecnologia, su about://network-error/-106. Ma sapevi che vengono giocati 270 milioni di giochi Dino di Chrome ogni mese?

Pagina offline di Chrome con il gioco Dino di Chrome.
Premi la barra spaziatrice per giocare.

Un altro fatto che è forse più utile sapere e che potresti non conoscere è che in modalità arcade puoi giocare con un gamepad. Il supporto del gamepad è stato aggiunto circa un anno fa al momento di scrivere questo articolo in un commit di Reilly Grant. Come puoi vedere, il gioco, come il resto del progetto Chromium, è completamente open source. In questo post, voglio mostrarti come utilizzare l'API Gamepad.

Utilizzare l'API Gamepad

Rilevamento delle funzionalità e supporto dei browser

L'API Gamepad offre un ottimo supporto per i browser sia su computer che su dispositivi mobili. Puoi rilevare se l'API Gamepad è supportata utilizzando il seguente snippet:

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

In che modo il browser rappresenta un gamepad

Il browser rappresenta i gamepad come oggetti Gamepad. Un Gamepad ha le seguenti proprietà:

  • id: una stringa di identificazione per il gamepad. Questa stringa identifica il brand o lo stile del dispositivo gamepad collegato.
  • displayId: il VRDisplay.displayId di un VRDisplay associato (se pertinente).
  • index: l'indice del gamepad nel navigatore.
  • connected: indica se il gamepad è ancora connesso al sistema.
  • hand: un enum che definisce la mano con cui viene tenuto il controller o con cui è più probabile che venga tenuto.
  • timestamp: l'ultima volta che sono stati aggiornati i dati relativi a questo gamepad.
  • mapping: la mappatura dei pulsanti e degli assi in uso per questo dispositivo, "standard" o "xr-standard".
  • pose: un oggetto GamepadPose rappresentante le informazioni sulla posa associate a un controller WebVR.
  • axes: un array di valori per tutti gli assi del gamepad, normalizzato linearmente all'intervallo -1.0-1.0.
  • buttons: un array di stati dei pulsanti per tutti i pulsanti del gamepad.

Tieni presente che i pulsanti possono essere digitali (premuti o meno) o analogici (ad esempio premuti al 78%). Per questo motivo, i pulsanti vengono registrati come oggetti GamepadButton, con i seguenti attributi:

  • pressed: lo stato premuto del pulsante (true se il pulsante è premuto e false se non lo è).
  • touched: lo stato toccato del pulsante. Se il pulsante è in grado di rilevare il tocco, questa proprietà è true se il pulsante viene toccato e false in caso contrario.
  • value: per i pulsanti con un sensore analogico, questa proprietà rappresenta la quantità di pressione del pulsante, normalizzata linearmente nell'intervallo 0.0-1.0.
  • hapticActuators: un array contenente oggetti GamepadHapticActuator ognuno dei quali rappresenta l'hardware di feedback aptico disponibile sul controller.

Un'altra cosa che potresti riscontrare, a seconda del browser e del gamepad in uso, è una proprietà vibrationActuator. Consente due tipi di effetti rumble:

  • Dual-Rumble: l'effetto di feedback aptico generato da due attuatori a massa rotante eccentrici, uno in ogni impugnatura del gamepad.
  • Trigger-Rumble: l'effetto di feedback aptico generato da due motori indipendenti, con un motore situato in ciascuno dei trigger del gamepad.

La seguente panoramica schematica, presa direttamente dalle specifiche, mostra la mappatura e la disposizione dei pulsanti e degli assi su un gamepad generico.

Panoramica schematica delle mappature dei pulsanti e degli assi di un gamepad comune.
Rappresentazione visiva di un layout del gamepad standard (fonte).

Notifica quando viene collegato un gamepad

Per sapere quando è collegato un gamepad, ascolta l'evento gamepadconnected che si attiva sull'oggetto window. Quando l'utente collega un gamepad, che può avvenire tramite USB o Bluetooth, viene attivato un GamepadEvent contenente i dettagli del gamepad in una proprietà gamepad opportunamente denominata. Di seguito puoi vedere un esempio di un controller Xbox 360 che avevo in giro (sì, mi piacciono i giochi retrò).

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

Notifica quando un gamepad viene disconnesso

La ricezione di notifiche relative alle disconnessioni del gamepad avviene in modo analogo al modo in cui vengono rilevate le connessioni. Questa volta l'app ascolta l'evento gamepaddisconnected. Nota come nell'esempio seguente connected ora è false quando scollego il controller 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
  */
});

Il gamepad nel loop di gioco

Per ottenere un gamepad, inizia con una chiamata a navigator.getGamepads(), che restituisce un array con Gamepad elementi. L'array in Chrome ha sempre una lunghezza fissa di quattro elementi. Se non sono collegati gamepad o se sono collegati meno di quattro gamepad, un elemento potrebbe essere semplicemente null. Assicurati sempre di controllare tutti gli elementi dell'array e tieni presente che i gamepad "ricordano" la loro posizione e potrebbero non essere sempre presenti nella prima posizione disponibile.

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

Se uno o più gamepad sono collegati, ma navigator.getGamepads() continua a segnalare null elementi, potrebbe essere necessario "riattivare" ogni gamepad premendo uno dei relativi pulsanti. Puoi quindi eseguire il polling degli stati del gamepad nel loop di gioco come mostrato nel codice seguente.

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

L'attuatore di vibrazione

La proprietà vibrationActuator restituisce un oggetto GamepadHapticActuator, che corrisponde a una configurazione di motori o altri attuatori in grado di applicare una forza ai fini del feedback aptico. Gli effetti aptico possono essere riprodotti chiamando Gamepad.vibrationActuator.playEffect(). Gli unici tipi di effetti validi sono 'dual-rumble' e 'trigger-rumble'.

Effetti rumble supportati

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

Doppio feedback a vibrazione

La tecnologia Dual-rumble descrive una configurazione aptica con un motore di vibrazione con massa rotante eccentrica in ogni impugnatura di un gamepad standard. In questa configurazione, ognuno dei due motori è in grado di far vibrare l'intero gamepad. Le due masse sono disuguali in modo che gli effetti di ciascuna possano essere combinati per creare effetti tattili più complessi. Gli effetti dual-rumble sono definiti da quattro parametri:

  • duration: imposta la durata dell'effetto di vibrazione in millisecondi.
  • startDelay: imposta la durata del ritardo fino all'avvio della vibrazione.
  • strongMagnitude e weakMagnitude: imposta i livelli di intensità di vibrazione per i motori con massa rotante eccentrica più pesanti e più leggeri, normalizzati all'intervallo 0.0-1.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,
  });
};

Attivare il feedback aptico

Il feedback aptico dei trigger è l'effetto generato da due motori indipendenti, con un motore in ciascuno dei trigger del gamepad.

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

Integrazione con i criteri relativi alle autorizzazioni

La specifica dell'API Gamepad definisce una funzionalità controllata da norme identificata dalla stringa "gamepad". Il valore predefinito di allowlist è "self". I criteri di autorizzazione di un documento determinano se i contenuti del documento possono accedere a navigator.getGamepads(). Se la funzionalità viene disattivata in qualsiasi documento, nessun contenuto del documento potrà utilizzare navigator.getGamepads() e non verranno attivati gli eventi gamepadconnected e gamepaddisconnected.

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

Demo

Un'demo del tester del gamepad è incorporata nell'esempio seguente. Il codice sorgente è disponibile su Glitch. Prova la demo collegando un gamepad tramite USB o Bluetooth e premendo uno dei suoi pulsanti o spostando uno dei suoi assi.

Bonus: gioca a Dino di Chrome su web.dev

Puoi giocare a Dino di Chrome con il tuo gamepad su questo stesso sito. Il codice sorgente è disponibile su GitHub. Dai un'occhiata all'implementazione del polling del gamepad in trex-runner.js e nota come emula le pressioni dei tasti.

Affinché la demo del gamepad Dino di Chrome funzioni, ho estratto il gioco Dino di Chrome dal progetto Chromium principale (aggiornando un impegno precedente di Arnelle Ballane), l'ho inserito in un sito autonomo, ho esteso l'implementazione dell'API gamepad esistente aggiungendo effetti di attenuazione e vibrazione, ho creato una modalità a schermo intero e Mehul Satardekar ha contribuito con un'implementazione della modalità oscura. Buon divertimento!

Ringraziamenti

Questo documento è stato esaminato da François Beaufort e Joe Medley. La specifica dell'API Gamepad è redatta da Steve Agoston, James Hollyer e Matt Reynolds. I precedenti editor di specifiche sono Brandon Jones, Scott Graham e Ted Mielczarek. Le specifiche delle estensioni per gamepad sono redatte da Brandon Jones. Immagine hero di Laura Torrent Puig.