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?
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
: ilVRDisplay.displayId
di unVRDisplay
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 oggettoGamepadPose
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 efalse
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 efalse
in caso contrario.value
: per i pulsanti con un sensore analogico, questa proprietà rappresenta la quantità di pressione del pulsante, normalizzata linearmente 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 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.
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
eweakMagnitude
: imposta i livelli di intensità di vibrazione per i motori con massa rotante eccentrica più pesanti e più leggeri, normalizzati all'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,
});
};
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!
Link utili
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.