Scopri come utilizzare l'API Gamepad per portare i tuoi browser game a un livello superiore.
L'easter egg della pagina offline di Chrome è uno dei segreti peggio custoditi della storia ([citation needed],
ma l'affermazione è stata fatta per un effetto drammatico). Se premi il tasto Spazio o, sui dispositivi mobili, tocchi il dinosauro, la pagina offline diventa un gioco arcade. Forse sai già che non devi necessariamente andare offline per giocare: in Chrome, puoi semplicemente andare all'indirizzo about://dino o, se sei un geek, all'indirizzo about://network-error/-106. Ma sapevi che
ogni mese vengono giocate
270 milioni di partite al gioco del dinosauro di Chrome?
Un altro fatto che probabilmente è più utile sapere e di cui potresti non essere a conoscenza è che in modalità arcade puoi giocare con un gamepad. Il supporto per i gamepad è stato aggiunto circa un anno fa, al momento della stesura di questo articolo, in un commit di Reilly Grant. Come puoi vedere, il gioco, proprio come il resto del progetto Chromium, è completamente open source. In questo post, voglio mostrarti come utilizzare l'API Gamepad.
Utilizza l'API Gamepad
Rilevamento delle funzionalità e supporto del browser
L'API Gamepad ha un ottimo supporto dei 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!
}
Come 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 gamepad connesso.displayId: ilVRDisplay.displayIddi unVRDisplayassociato (se pertinente).index: l'indice del gamepad nel navigatore.connected: indica se il gamepad è ancora connesso al sistema.hand: un enum che definisce in quale mano viene tenuto il controller o in quale mano è più probabile che venga tenuto.timestamp: l'ultima volta che i dati di questo gamepad sono stati aggiornati.mapping: la mappatura dei pulsanti e degli assi in uso per questo dispositivo,"standard"o"xr-standard".pose: un oggettoGamepadPoseche rappresenta le informazioni sulla posa associate a un controller WebVR.axes: un array di valori per tutti gli assi del gamepad, normalizzati in modo lineare 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 (premuto o non premuto) o analogici (ad esempio, premuto al 78%). Per questo motivo, i pulsanti vengono segnalati come oggetti GamepadButton, con i seguenti attributi:
pressed: lo stato di pressione del pulsante (truese il pulsante è premuto efalsese non è premuto).touched: lo stato di tocco del pulsante. Se il pulsante è in grado di rilevare il tocco, questa proprietà ètruese il pulsante viene toccato efalsein caso contrario.value: per i pulsanti con un sensore analogico, questa proprietà rappresenta la quantità di pressione esercitata sul pulsante, normalizzata in modo lineare nell'intervallo0.0-1.0.hapticActuators: un array contenente oggettiGamepadHapticActuator, 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 che hai,
è una proprietà vibrationActuator. Consente due tipi di effetti di vibrazione:
- Dual-Rumble: l'effetto di feedback aptico generato da due attuatori di massa rotante eccentrica, 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, tratta direttamente dalla specifica, mostra la mappatura e la disposizione dei pulsanti e degli assi su un gamepad generico.
Notifica quando viene collegato un gamepad
Per sapere quando un gamepad è connesso, ascolta l'evento gamepadconnected che si attiva sull'oggetto window. Quando l'utente connette un gamepad, tramite USB o Bluetooth, viene attivato un evento GamepadEvent che contiene i dettagli del gamepad in una proprietà gamepad con un nome appropriato.
Di seguito puoi vedere un esempio di un controller Xbox 360 che avevo in giro (sì, mi piace il 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"}
*/
});
Notifica quando un gamepad si disconnette
La ricezione di notifiche relative alle disconnessioni del gamepad avviene in modo analogo al rilevamento delle connessioni.
Questa volta l'app rimane in ascolto dell'evento gamepaddisconnected. Nota come nell'esempio seguente connected diventa 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, devi prima chiamare navigator.getGamepads(), che restituisce un array con Gamepad elementi. 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 semplicemente null. Assicurati sempre di controllare tutti gli elementi dell'array e tieni presente che i gamepad "ricordano" il loro 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,
potresti dover "attivare" ogni gamepad premendo uno dei suoi 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 che possono applicare una forza ai fini del feedback aptico. Gli effetti aptici possono essere riprodotti chiamando Gamepad.vibrationActuator.playEffect(). Gli unici tipi di effetti validi sono 'dual-rumble' e 'trigger-rumble'.
Effetti di vibrazione 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.
}
Doppia vibrazione
Dual-rumble descrive una configurazione aptica con un motore vibrazione a massa rotante eccentrica in ogni impugnatura di un gamepad standard. In questa configurazione, entrambi i motori sono in grado di far vibrare l'intero gamepad. Le due masse sono diverse, in modo che gli effetti di ciascuna possano essere combinati per creare effetti aptici più complessi. Gli effetti di vibrazione doppia sono definiti da quattro parametri:
duration: imposta la durata dell'effetto di vibrazione in millisecondi.startDelay: imposta la durata del ritardo prima dell'inizio della vibrazione.strongMagnitudeeweakMagnitude: imposta i livelli di intensità della vibrazione per i motori a massa rotante eccentrica più pesanti e più leggeri, normalizzati nell'intervallo0.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,
});
};
Vibrazione dei grilletti
La vibrazione dei trigger è l'effetto di feedback aptico generato da due motori indipendenti, uno situato 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 Permissions Policy
La specifica dell'API Gamepad definisce una
funzionalità controllata da policy identificata dalla
stringa "gamepad". Il valore predefinito di allowlist è "self". La policy sulle autorizzazioni di un documento determina se i contenuti di quel documento sono autorizzati ad accedere a navigator.getGamepads(). Se disattivato in un documento, nessun contenuto del documento potrà utilizzare navigator.getGamepads(), né verranno attivati gli eventi gamepadconnected e gamepaddisconnected.
<iframe src="index.html" allow="gamepad"></iframe>
Demo
Nell'esempio seguente è incorporata una demo del tester del gamepad. Il codice sorgente è disponibile su GitHub. 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 sito. Il codice sorgente è disponibile su GitHub.
Dai un'occhiata all'implementazione del polling del gamepad in trex-runner.js e osserva come emula la pressione dei tasti.
Per far funzionare la demo del gamepad Dino di Chrome, ho estratto il gioco Dino di Chrome dal progetto Chromium principale (aggiornando un tentativo precedente di Arnelle Ballane), l'ho inserito in un sito autonomo, ho esteso l'implementazione dell'API Gamepad esistente aggiungendo effetti di vibrazione e abbassamento, ho creato una modalità a schermo intero e Mehul Satardekar ha contribuito con un'implementazione della modalità Buio. Buon divertimento!
Link utili
Ringraziamenti
Questo documento è stato rivisto da François Beaufort e Joe Medley. La specifica dell'API Gamepad è modificata da Steve Agoston, James Hollyer e Matt Reynolds. Gli ex editor delle specifiche sono Brandon Jones, Scott Graham e Ted Mielczarek. La specifica Gamepad Extensions è modificata da Brandon Jones.