Scopri il potenziale di WebGL con gli scenari infiniti generati proceduralmente di questo gioco di guida casual.
Slow Roads è un gioco di guida casual incentrato su scenari generati proceduralmente in modo infinito, il tutto ospitato nel browser come applicazione WebGL. Per molti, un'esperienza così intensa potrebbe sembrare fuori luogo nel contesto limitato del browser e, in effetti, correggere questo atteggiamento è stato uno dei miei obiettivi con questo progetto. In questo articolo analizzerò alcune delle tecniche che ho utilizzato per superare l'ostacolo del rendimento nel mio intento di mettere in evidenza il potenziale spesso trascurato del 3D sul web.
Sviluppo 3D nel browser
Dopo aver rilasciato la funzionalità Strade a traffico lento, ho notato un commento ricorrente nei feedback: "Non sapevo che fosse possibile nel browser". Se condividi questo sentimento, non sei certamente una minoranza; secondo il sondaggio State of JS 2022, circa l'80% degli sviluppatori non ha ancora sperimentato WebGL. Per me è un peccato che tanto potenziale possa andare sprecato, soprattutto per quanto riguarda i giochi basati su browser. Con Slow Roads spero di mettere WebGL ancora più sotto i riflettori e forse di ridurre il numero di sviluppatori che si fermano alla frase "motore di gioco JavaScript ad alte prestazioni".
WebGL può sembrare misterioso e complesso per molti, ma negli ultimi anni i suoi ecosistemi di sviluppo sono molto maturati e ora offrono strumenti e librerie molto efficaci e pratici. Ora è più facile che mai per gli sviluppatori front-end incorporare l'esperienza utente 3D nel loro lavoro, anche senza esperienza pregressa nella computer grafica. Three.js, la principale libreria WebGL, funge da base per molte espansioni, tra cui react-three-fiber che introduce i componenti 3D nel framework React. Ora esistono anche editor di giochi completi basati sul web come Babylon.js o PlayCanvas che offrono un'interfaccia familiare e toolchain integrate.
Tuttavia, nonostante l'utilità straordinaria di queste librerie, i progetti ambiziosi sono inevitabilmente vincolati da limitazioni tecniche. Gli scettici nei confronti dell'idea di giochi basati su browser potrebbero sottolineare che JavaScript è a thread singolo e con risorse limitate. Tuttavia, se superi queste limitazioni, scoprirai il valore nascosto: nessun'altra piattaforma offre la stessa accessibilità immediata e la stessa compatibilità di massa consentita dal browser. Gli utenti di qualsiasi sistema compatibile con i browser possono iniziare a riprodurre i contenuti con un solo clic, senza dover installare applicazioni e senza dover accedere ai servizi. Per non parlare del fatto che gli sviluppatori possono usufruire della comodità di avere a disposizione framework front-end solidi per creare l'interfaccia utente o gestire la rete per le modalità multiplayer. A mio avviso, questi valori sono ciò che rende il browser una piattaforma così eccellente sia per i giocatori che per gli sviluppatori e, come dimostrato da Slow Roads, le limitazioni tecniche potrebbero spesso essere riconducibili a un problema di progettazione.
Ottenere prestazioni fluide su strade a traffico lento
Poiché gli elementi principali di strade a traffico lento richiedono movimenti ad alta velocità e la generazione di scenari costosi, la necessità di un funzionamento fluido ha influito su ogni mia decisione di progettazione. La mia strategia principale è stata iniziare con un design del gameplay semplificato che consentisse di utilizzare scorciatoie contestuali all'interno dell'architettura del motore. Lo svantaggio è che, per ottenere un sistema minimalista, è necessario rinunciare ad alcune funzionalità, ma il risultato è un sistema personalizzato e iperottimizzato che funziona perfettamente su diversi browser e dispositivi.
Di seguito è riportata una suddivisione dei componenti chiave che mantengono le strade a traffico lento snelle.
Definire il motore dell'ambiente in base al gameplay
Essendo il componente chiave del gioco, il motore di generazione dell'ambiente è inevitabilmente costoso e occupa giustamente la maggior parte del budget per memoria e calcolo. Il trucco utilizzato qui consiste nel pianificare e distribuire i calcoli intensivi su un periodo di tempo, in modo da non interrompere il frame rate con picchi di prestazioni.
L'ambiente è composto da riquadri di geometria, che differiscono per dimensioni e risoluzione (classificati come "livelli di dettaglio" o LoD) a seconda di quanto saranno vicini alla videocamera. Nei giochi tipici con una telecamera libera, è necessario caricare e scaricare costantemente diversi livelli di dettaglio per mostrare in dettaglio l'ambiente circostante del giocatore, ovunque decida di andare. Questa può essere un'operazione costosa e inutilizzata, soprattutto quando l'ambiente stesso viene generato dinamicamente. Fortunatamente, questa convenzione può essere completamente sovvertita nelle strade a traffico lento grazie all'aspettativa contestuale che l'utente rimanga sulla strada. La geometria ad alto dettaglio può essere invece riservata al corridoio stretto che costeggia direttamente il percorso.

La linea di mezzeria della strada stessa viene generata molto prima dell'arrivo del giocatore, consentendo di prevedere con precisione quando e dove saranno necessari i dettagli dell'ambiente. Il risultato è un sistema snello che può pianificare in modo proattivo il lavoro costoso, generando solo il minimo necessario in ogni momento e senza sprecare energie per dettagli che non verranno visualizzati. Questa tecnica è possibile solo perché la strada è un percorso singolo e senza ramificazioni, un buon esempio di compromessi nel gameplay che consentono di adottare scorciatoie architettoniche.

Essere esigenti con le leggi della fisica
La simulazione fisica è seconda solo alla richiesta di calcolo del motore dell'ambiente. Slow Roads utilizza un motore fisico personalizzato e minimale che prende tutte le scorciatoie disponibili.
Il risparmio principale è evitare di simulare troppi oggetti in primo luogo, adottando un contesto zen minimalista, tralasciando elementi come collisioni dinamiche e oggetti distruttibili. L'assunto che il veicolo rimanga sulla strada significa che le collisioni con oggetti fuori strada possono essere ragionevolmente ignorate. Inoltre, la codifica della strada come linea mediana sparsa consente di utilizzare trucchi eleganti per il rilevamento rapido delle collisioni con il manto stradale e le guardrail, il tutto in base a un controllo della distanza dal centro della strada. La guida fuoristrada diventa quindi più costosa, ma questo è un altro esempio di un giusto compromesso adatto al contesto del gameplay.
Gestione dell'impronta della memoria
Poiché è un'altra risorsa limitata dal browser, è importante gestire la memoria con attenzione, nonostante il fatto che JavaScript sia sottoposto a raccolta dei rifiuti. Può essere facile trascurare questo aspetto, ma dichiarare anche piccole quantità di nuova memoria all'interno di un loop di gioco può generare problemi significativi se eseguito a 60 Hz. Oltre a consumare le risorse dell'utente in un contesto in cui è probabile che esegua il multitasking, la raccolta di grandi quantità di dati inutilizzati può richiedere diversi frame per essere completata, causando notevoli balzi. Per evitare ciò, la memoria del loop può essere preallocata nelle variabili di classe all'inizializzazione e riciclata in ogni frame.

È inoltre molto importante che le strutture di dati più pesanti, come le geometrie e i relativi buffer di dati associati, vengano gestite in modo economico. In un gioco generato in modo infinito come Slow Roads, la maggior parte della geometria esiste su una sorta di tapis roulant: quando un vecchio pezzo scompare in lontananza, le sue strutture di dati possono essere archiviate e riutilizzate per un nuovo pezzo del mondo, un pattern di progettazione noto come pooling di oggetti.
Queste pratiche aiutano a dare la priorità all'esecuzione snella, a discapito di una certa semplicità del codice. In contesti ad alte prestazioni, è importante tenere presente che a volte le funzionalità di praticità vengono prese in prestito dal client a vantaggio dello sviluppatore. Ad esempio, metodi come Object.keys()
o Array.map()
sono incredibilmente utili, ma è facile trascurare il fatto che ognuno crea una nuova matrice per il valore restituito. Comprendere il funzionamento interno di queste scatole nere può aiutarti a perfezionare il codice ed evitare cali di rendimento insidiosi.
Riduzione del tempo di caricamento con asset generati proceduralmente
Sebbene le prestazioni di runtime debbano essere la preoccupazione principale per gli sviluppatori di giochi, i soliti assiomi relativi al tempo di caricamento iniziale della pagina web rimangono validi. Gli utenti potrebbero essere più indulgenti quando accedono consapevolmente a contenuti pesanti, ma i tempi di caricamento lunghi possono comunque essere dannosi per l'esperienza, se non per la fidelizzazione degli utenti. I giochi spesso richiedono asset di grandi dimensioni sotto forma di texture, suoni e modelli 3D e, come minimo, questi asset devono essere compressi con attenzione, ove possibile, per ridurre al minimo i dettagli.
In alternativa, la generazione procedurale degli asset sul client può evitare trasferimenti lunghi. Si tratta di un vantaggio enorme per gli utenti con connessioni lente e offre allo sviluppatore un controllo più diretto sulla struttura del gioco, non solo per la fase di caricamento iniziale, ma anche per l'adattamento dei livelli di dettaglio a diverse impostazioni di qualità.
La maggior parte della geometria delle strade a traffico lento è generata proceduralmente e semplicistica, con shader personalizzati che combinano più texture per aggiungere dettagli. Lo svantaggio è che queste texture possono essere asset di grandi dimensioni, anche se esistono ulteriori opportunità di risparmio, con metodi come la texturing stocastica in grado di ottenere maggiori dettagli da piccole texture di origine. A livello estremo, è anche possibile generare texture interamente sul client con strumenti come texgen.js. Lo stesso vale per l'audio, con l'API Web Audio che consente la generazione di suoni con i nodi audio.
Grazie agli asset procedurali, la generazione dell'ambiente iniziale richiede in media solo 3,2 secondi. Per sfruttare al meglio il ridotto volume di download iniziale, una semplice schermata di benvenuto accoglie i nuovi visitatori e posticipa l'inizializzazione costosa della scena fino a quando non viene premuto un pulsante di conferma. Inoltre, funge da comodo buffer per le sessioni con errore di recapito, riducendo al minimo lo spreco di trasferimento di asset caricati dinamicamente.
Un approccio agile all'ottimizzazione tardiva
Ho sempre considerato la base di codice di Slow Roads sperimentale e, di conseguenza, ho adottato un approccio estremamente agile allo sviluppo. Quando si utilizza un'architettura di sistema complessa e in rapida evoluzione, può essere difficile prevedere dove possono verificarsi i colli di bottiglia importanti. L'attenzione deve essere rivolta all'implementazione rapida delle funzionalità desiderate, anziché alla loro implementazione corretta, per poi procedere a ritroso per ottimizzare i sistemi dove contano davvero. Il profiler delle prestazioni in Chrome DevTools è di inestimabile valore per questo passaggio e mi ha aiutato a diagnosticare alcuni problemi importanti con le versioni precedenti del gioco. Il tuo tempo in qualità di sviluppatore è prezioso, quindi assicurati di non sprecarlo a risolvere problemi che potrebbero rivelarsi insignificanti o ridondanti.
Monitoraggio dell'esperienza utente
Durante l'implementazione di tutti questi trucchi, è importante assicurarsi che il gioco funzioni come previsto in produzione. Supportare una gamma di funzionalità hardware è un aspetto fondamentale di qualsiasi sviluppo di giochi, ma i giochi web possono avere come target uno spettro molto più ampio che comprende contemporaneamente computer di fascia alta e dispositivi mobili di dieci anni fa. Il modo più semplice per affrontare questo problema è offrire impostazioni per adattare i colli di bottiglia più probabili nella base di codice, sia per le attività che richiedono l'uso intensivo di GPU e CPU, come rivelato dal profiler.
Tuttavia, il profiling sul tuo computer può coprire solo un numero limitato di aspetti, quindi è importante chiudere il ciclo di feedback con gli utenti in qualche modo. Per le strade a traffico lento eseguo analisi semplici che generano report sul rendimento insieme a fattori contestuali come la risoluzione dello schermo. Questi dati vengono inviati a un backend Node di base utilizzando socket.io, insieme a eventuali feedback scritti inviati dall'utente tramite il form in-game. All'inizio, questi dati hanno rilevato molti problemi importanti che potevano essere attenuati con semplici modifiche all'esperienza utente, come l'evidenziazione del menu Impostazioni quando viene rilevato un FPS costantemente basso o l'avviso che un utente potrebbe dover attivare l'accelerazione hardware se le prestazioni sono particolarmente scarse.
Le strade a traffico lento
Anche dopo aver adottato tutte queste misure, rimane una parte significativa della base di giocatori che deve giocare su impostazioni inferiori, principalmente quelli che utilizzano dispositivi leggeri privi di GPU. Sebbene l'intervallo di impostazioni di qualità disponibili porti a una distribuzione abbastanza uniforme delle prestazioni, solo il 52% dei giocatori raggiunge più di 55 FPS.

Fortunatamente, ci sono ancora molte opportunità per risparmiare in termini di prestazioni. Oltre ad aggiungere ulteriori trucchi di rendering per ridurre la domanda di GPU, spero di sperimentare con i web worker la parallelizzazione della generazione dell'ambiente nel prossimo futuro e potrei eventualmente riscontrare la necessità di incorporare WASM o WebGPU nella base di codice. Qualsiasi spazio di manovra che riuscirò a liberare consentirà di creare ambienti più ricchi e diversificati, che saranno l'obiettivo permanente per il resto del progetto.
Come progetto per hobby, Slow Roads è stato un modo estremamente soddisfacente per dimostrare quanto possano essere sorprendentemente elaborati, performanti e popolari i giochi in browser. Se ho suscitato il tuo interesse per WebGL, sappi che dal punto di vista tecnologico, Slow Roads è un esempio piuttosto superficiale delle sue funzionalità complete. Consiglio vivamente ai lettori di esplorare la vetrina di Three.js e, in particolare, chi è interessato allo sviluppo di giochi web è il benvenuto nella community su webgamedev.com.