Gioca a Dino di Chrome con il tuo gamepad

Scopri come usare l'API Gamepad per portare i tuoi giochi web al livello successivo.

L'Easter egg di Chrome nella pagina offline è uno dei peggiori segreti della storia ([citation needed], ma questa dichiarazione è stata fatta per questo effetto drammatico). Se premi la barra spaziatrice o tocchi il dinosauro sui dispositivi mobili, la pagina offline diventa un gioco arcade giocabile. Forse sai già che non devi necessariamente disconnetterti quando hai voglia di giocare: in Chrome puoi semplicemente andare su about://dino oppure, se sei un geek che è in te, puoi andare su about://network-error/-106. Ma sapevi che ci sono 270 milioni di giochi di 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 probabilmente è più utile sapere e di cui potresti non essere a conoscenza è che nella modalità arcade è possibile giocare con un gamepad. Il supporto per i Gamepad è stato aggiunto circa un anno fa, al momento della stesura di questo documento, in un commit di Reilly Grant. Come puoi vedere, il gioco, proprio come il resto del progetto Chromium, è completamente open source. In questo post ti mostrerò come utilizzare l'API Gamepad.

Utilizzare l'API Gamepad

Rilevamento delle funzionalità e supporto del browser

L'API Gamepad offre un supporto del browser eccezionale, sia per computer che per 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 la marca o lo stile del dispositivo gamepad collegato.
  • displayId: VRDisplay.displayId di un VRDisplay associato (se pertinente).
  • index: l'indice del gamepad nel navigatore.
  • connected: indica se il gamepad è ancora collegato al sistema.
  • hand: un'enumerazione che definisce la mano in cui è tenuto, o che è più probabile che lo faccia, il controller.
  • timestamp: l'ultima volta che sono stati aggiornati i dati per questo gamepad.
  • mapping: la mappatura del pulsante e degli assi in uso per questo dispositivo "standard" o "xr-standard".
  • pose: un oggetto GamepadPose che rappresenta le informazioni sulla posa associate a un controller WebVR.
  • axes: un array di valori per tutti gli assi del gamepad, normalizzato linearmente nell'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 non premuti) o analogici (ad esempio, premuti al 78%). Questo è il motivo per cui i pulsanti vengono segnalati come oggetti GamepadButton, con i seguenti attributi:

  • pressed: lo stato premuto del pulsante (true se il pulsante viene premuto e false se non viene premuto.
  • 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 dotati di un sensore analogico, questa proprietà rappresenta il livello di pressione del pulsante, normalizzato linearmente nell'intervallo compreso tra 0.0 e 1.0.
  • hapticActuators: un array contenente oggetti GamepadHapticActuator, ognuno dei quali rappresenta l'hardware con feedback aptico disponibile sul controller.

Un altro problema che potresti riscontrare, a seconda del browser e del gamepad che hai, è una proprietà vibrationActuator. Consente due tipi di effetti per il rumore:

  • Dual Rumble: l'effetto feedback aptico generato da due eccentrici attuatori di massa rotanti, uno in ciascuna impugnatura del gamepad.
  • Rumble: l'effetto di feedback aptico generato da due motori indipendenti, con un motore situato in ciascuno dei grilletti del gamepad.

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

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

Notifica quando un gamepad viene collegato

Per sapere quando è connesso un gamepad, esamina l'evento gamepadconnected che si attiva sull'oggetto window. Quando l'utente collega un gamepad, che può avvenire tramite USB o Bluetooth, si attiva un GamepadEvent che contiene i dettagli del gamepad in una proprietà gamepad con un nome appropriato. Di seguito puoi vedere un esempio di un controller per Xbox 360 che avevo in giro (sì, mi piacciono i giochi rétro).

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 di disconnessione di un gamepad

La notifica delle disconnessioni del gamepad avviene in modo analogo al modo in cui vengono rilevate le connessioni. Questa volta l'app rimane in ascolto dell'evento gamepaddisconnected. Tieni presente che nell'esempio seguente connected adesso è 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 ciclo di gioco

Per bloccare un gamepad, inizia con una chiamata a navigator.getGamepads(), che restituisce un array con elementi Gamepad. L'array in Chrome ha sempre una lunghezza fissa di quattro elementi. Se sono collegati zero o meno di quattro gamepad, un elemento potrebbe essere solo null. Assicurati sempre di controllare tutti gli elementi dell'array e tieni presente che i gamepad "ricordano" il proprio slot e potrebbero non essere sempre presenti nel primo slot disponibile.

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

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

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

Attuatore della vibrazione

La proprietà vibrationActuator restituisce un oggetto GamepadHapticActuator, che corrisponde a una configurazione di motori o altri attuatori che possono applicare una forza ai fini del feedback aptico. Gli effetti aptici possono essere riprodotti chiamando Gamepad.vibrationActuator.playEffect(). L'unico tipo di effetto valido è 'dual-rumble'. Il doppio rumore descrive una configurazione aptica con un motore di vibrazione di massa rotante eccentrico su ciascun manico di un gamepad standard. In questa configurazione, uno dei due motori è in grado di far vibrare l'intero gamepad. Le due masse sono disuguali, quindi i due effetti possono essere combinati per creare effetti aptici 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 prima dell'attivazione della vibrazione.
  • strongMagnitude e weakMagnitude: imposta i livelli di intensità della vibrazione per i motori con massa rotante eccentrica più pesanti e più leggeri, normalizzati sull'intervallo 0.0-1.0.

Effetti del rumore 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 rumore

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

Rumore dell'attivatore

// 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 le norme relative alle autorizzazioni

Le specifiche dell'API Gamepad definiscono una funzionalità controllata dai criteri identificata dalla stringa "gamepad". Il valore predefinito di allowlist è "self". Le norme relative alle autorizzazioni di un documento determinano se qualsiasi contenuto nel documento è autorizzato ad accedere a navigator.getGamepads(). Se la disattivi 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

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

Bonus: gioca a Dino di Chrome su web.dev

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

Per far funzionare la demo del dino Gamepad di Chrome, ho strappato il gioco dino di Chrome dal progetto principale di Chromium (aggiornando un progetto precedente di Arnelle Ballane), l'ho posizionato su un sito autonomo, esteso l'implementazione esistente dell'API del gamepad aggiungendo effetti di attenuazione e vibrazione, creato una modalità a schermo intero e Mehul Satardekar ha contribuito a un'implementazione della modalità Buio. Buon divertimento!

Ringraziamenti

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