Costruire un gioco WebGL multipiattaforma con Babylon.js
Pubblicato: 2022-03-10Ecco una sfida per te: che ne dici di costruire un gioco 3D durante il fine settimana? Babylon.js è un framework JavaScript per la creazione di giochi 3D con HTML5, WebGL e Web Audio , creato da te e dal team di Babylon.js. Per celebrare la nuova versione 2.3 della libreria, abbiamo deciso di creare una nuova demo chiamata "Sponza" per evidenziare cosa si può fare con il motore WebGL e HTML5 quando si tratta di creare grandi giochi al giorno d'oggi.
L'idea era quella di creare un'esperienza coerente, simile, se non identica, su tutte le piattaforme supportate da WebGL e di provare a raggiungere le funzionalità delle app native. In questo articolo, spiegherò come funziona tutto insieme, insieme alle varie sfide che abbiamo affrontato e alle lezioni che abbiamo imparato durante la sua costruzione.
Ulteriori letture su SmashingMag:
- Creazione di shader con Babylon.js
- Utilizzo dell'API Gamepad nei giochi Web
- Introduzione alla modellazione poligonale e Three.js
- Come creare una drum machine a 8 bit reattiva
Per raggiungere questo obiettivo, Sponza utilizza una serie di funzionalità HTML5 come WebGL, Web Audio, nonché Pointer Events (ampiamente supportato ora grazie a jQuery PEP polyfill), Gamepad API, IndexedDB, HTML5 AppCache, CSS3 transition/animation, flexbox e Fullscreen API. Puoi testare la demo Sponza sul tuo desktop, dispositivo mobile o Xbox One.
Alla scoperta della demo
Per prima cosa, inizierai con una sequenza animata automaticamente dando i crediti a chiunque abbia costruito la scena. La maggior parte dei membri del team proviene dalla scena demo. Scoprirai che questa è una parte importante della cultura degli sviluppatori 3D. Da parte mia, ero su Atari mentre David Catuhe era su Amiga che è ancora una fonte regolare di conflitti tra noi, che ci crediate o no. Stavo programmando un po', ma principalmente componevo la musica nel mio gruppo demo. Ero un grande fan di Future Crew e più specificamente di Purple Motion, il mio compositore di scene demo preferito di tutti i tempi. Ma non deviamo dall'argomento.
Per Sponza, ecco i contributori:
- Michel Rousseau alias "Mitch" ha realizzato straordinarie animazioni visive e ottimizzazioni di rendering in qualità di artista 3D. Ha preso il modello Sponza fornito gratuitamente da Crytek sul loro sito Web e ha utilizzato l'esportatore 3DS Max per generare ciò che vedi.
- David Catuhe alias "deltakosh" e io abbiamo fatto la parte principale del motore Babylon.js e anche tutto il codice per la demo (caricatore personalizzato, effetti speciali per la modalità demo che usa i post-processi di dissolvenza in nero ecc.) così come un nuovo tipo di telecamera denominata “ UniversalCamera ” che gestisce tutti i tipi di input in modo generico.
- Ho composto la musica usando Renoise e la banca sonora della EastWest Symphonic Orchestra. Se sei interessato, ho già condiviso il mio flusso di lavoro e il mio processo nell'articolo sulla composizione della musica per il gioco World Monger per Windows 8 utilizzando il tracker Renoise e i plug-in VST East West
- Julien Moreau-Mathis ci ha aiutato costruendo un nuovo strumento per aiutare gli artisti 3D a finalizzare il lavoro tra gli strumenti di modellazione (3DS Max, Blender) e il risultato finale. Ad esempio, Michel lo ha utilizzato per testare e mettere a punto varie telecamere animate e per iniettare particelle nella scena.
Se attendi fino alla fine della sequenza automatica fino al “finale epico”, passerai automaticamente alla modalità interattiva. Se desideri ignorare la modalità demo, fai semplicemente clic sull'icona della fotocamera o premi A
sul tuo gamepad.
Nella modalità interattiva, se sei su un Mac o un PC, potrai muoverti all'interno della scena usando la tastiera/il mouse come un gioco FPS. Se sei su uno smartphone, potrai muoverti con un solo tocco (e 2
per ruotare la fotocamera). Infine, su Xbox One, puoi utilizzare il gamepad (o il desktop se ci colleghi un gamepad). Curiosità: su un PC Windows touch, puoi potenzialmente utilizzare 3 tipi di input contemporaneamente.
L'atmosfera è diversa nella modalità interattiva. Hai tre sorgenti audio tempesta posizionate casualmente nell'ambiente 3D, colpi di vento e crepacci su ogni angolo. Sui browser supportati (Chrome, Firefox, Opera e Safari), puoi anche passare dalla normale modalità vivavoce alla modalità cuffia cliccando sull'icona dedicata. Utilizzerà quindi il rendering audio binaurale di Web Audio per una simulazione audio più realistica, se stai ascoltando tramite le cuffie.
Per avere un'esperienza simile a un'app completa, abbiamo generato icone e riquadri per tutte le piattaforme. Ciò significa, ad esempio, che su Windows 8 ⁄ 10 puoi aggiungere l'app Web nel menu "Start". Abbiamo anche più taglie disponibili:
Prima offline!
Una volta che la demo è stata completamente caricata, puoi passare il telefono in modalità aereo per interrompere la connettività e fare clic sull'icona Sponza. L'app Web continuerà a fornire l'esperienza completa con il rendering WebGL, l'audio Web 3D e il supporto touch. Passa a schermo intero e letteralmente non sarai in grado di sentire la differenza tra la demo e un'esperienza app nativa.
Per questo stiamo usando il livello IndexedDB disponibile in modo nativo all'interno di Babylon.js. La scena (formato JSON) e le risorse (texture JPG/PNG e MP3 per musica e suoni) sono archiviate in IDB. Il livello IDB accoppiato con la cache dell'applicazione HTML5 fornisce quindi l'esperienza offline. Per saperne di più su questa parte e su come configurare il tuo gioco per ottenere risultati simili, puoi leggere l'articolo Utilizzo di IndexedDB per gestire le tue risorse WebGL 3D: condivisione di feedback e suggerimenti su Babylon.JS e Caching Resources in IndexedDB in Babylon.js
Xbox One si gode lo spettacolo
Ultimo ma non meno importante, la stessa demo funziona perfettamente in MS Edge su Xbox One:
Premere A
per passare alla modalità interattiva . Xbox One ti avviserà che ora puoi muoverti usando il tuo gamepad all'interno della scena 3D:
Quindi, ricapitoliamo brevemente.
La stessa base di codice funziona su Mac, Linux, Windows su MS Edge, Chrome, Firefox, Opera e Safari, su iPhone/iPad, su dispositivi Android con Chrome o Firefox, Firefox OS e su Xbox One! Non è bello? Essere in grado di indirizzare così tanti dispositivi con un'esperienza nativa a tutti gli effetti direttamente dal tuo server web?
Ho già condiviso il mio entusiasmo per le potenzialità della tecnologia in un precedente articolo su Il web: la prossima frontiera del gioco?
Hackera la scena con il livello di debug
Se vuoi capire come Michel sta padroneggiando la magia della modellazione 3D, puoi hackerare la scena usando lo strumento Babylon.js Debug Layer . Per abilitarlo su una macchina con tastiera, premi CMD/CTRL + SHIFT + D
e se stai utilizzando un gamepad su PC o Xbox, premi Y
. Si noti che la visualizzazione del livello di debug costa un po' di prestazioni a causa del lavoro di composizione che il motore di rendering deve eseguire. Quindi gli FPS visualizzati sono un po' meno importanti degli FPS reali che hai senza il livello di debug visualizzato.
Proviamolo su un PC, per esempio.
Avvicinati alla testa del leone e taglia il canale di urto dalla conduttura del nostro shader:
Dovresti vedere che la testa ora è meno realistica. Gioca con l'altro canale per controllare cosa sta succedendo.
Puoi anche interrompere il motore dinamico dei fulmini o disabilitare il motore delle collisioni per volare o muoverti attraverso i muri. Ad esempio, disabilita la casella di controllo " collisioni " e vola al primo piano. Metti la telecamera davanti alle bandiere rosse. Puoi vederli leggermente in movimento. Michel ha utilizzato il supporto per ossa/scheletri di Babylon.js per spostarli. Ora, disabilita l'opzione " scheletri " e dovrebbero smettere di muoversi:
Infine, puoi visualizzare l'albero delle maglie nell'angolo in alto a destra. Puoi abilitarli o disabilitarli per interrompere completamente il lavoro svolto da Michel:
Rimuovere le geometrie, i canali dello shader o alcune opzioni del motore può aiutarti a risolvere i problemi delle prestazioni su un dispositivo specifico per vedere cosa sta attualmente costando troppo. Puoi anche controllare se sei limitato dalla CPU o dalla GPU, anche se la maggior parte delle volte sarai limitato dalla CPU in WebGL a causa della natura mono-threading di JavaScript. Infine, lo strumento è anche molto utile per aiutarti a imparare come è stata costruita una scena dall'artista 3D.
A proposito, funziona abbastanza bene anche su Xbox One:
Sfide
Lungo la strada, abbiamo affrontato una serie di problemi e sfide per creare la demo. Esaminiamone alcuni in dettaglio.
Prestazioni WebGL e compatibilità multipiattaforma
Il lato della programmazione è stato probabilmente il più facile da affrontare in quanto è completamente gestito dal motore Babylon.js stesso. Stiamo utilizzando un'architettura di shader personalizzata che si adatta alla piattaforma cercando di trovare il miglior shader disponibile per il browser/GPU corrente utilizzando vari fallback. L'idea è quella di abbassare la qualità e la complessità del motore di rendering fino a quando non riusciamo a visualizzare qualcosa di significativo sullo schermo.
Babylon.js si basa principalmente su WebGL 1.0 per garantire che le esperienze 3D costruite su di esso funzionino praticamente ovunque. È stato creato pensando alla filosofia del web, quindi stiamo progressivamente migliorando il processo di compilazione dello shader. Questo è completamente trasparente per l'artista 3D che non vuole affrontare queste complessità per la maggior parte del tempo.
Tuttavia, l'artista 3D ha un ruolo molto importante nell'ottimizzazione delle prestazioni. Deve conoscere la piattaforma a cui si rivolge, le funzionalità supportate e le limitazioni. Non puoi prendere risorse provenienti da giochi AAA realizzati per GPU di fascia alta e DirectX 12 e integrarli semplicemente in un gioco in esecuzione su un motore WebGL. Direi che prendere di mira WebGL oggi è abbastanza simile al lavoro che dovrai fare per ottimizzare le esperienze sui dispositivi mobili, con un pizzico di JavaScript in più che deve essere altamente mono-thread.
Mitch è estremamente bravo proprio in questo: ottimizzare le trame, precalcolare il fulmine per incorporarlo nelle trame, ridurre il più possibile il numero di chiamate di estrazione, ecc. Ha anni di esperienza alle spalle e ha visto le varie generazioni di Hardware e motori 3D (da PowerVR/3DFX alle GPU di oggi) che hanno davvero aiutato a realizzare la demo.
Ha già condiviso alcune di queste nozioni di base nei suoi articoli su Real Time 3D: fare una demo per WebGL Purposes–Basics e ha già dimostrato più volte che puoi creare un'esperienza visiva piuttosto affascinante sul web con prestazioni elevate su piccole GPU integrate, ad es. Scene demo Mansion, Hill Valley o Espilit. Se sei interessato, prenditi del tempo per guardare il suo intervento su NGF2014 – Crea risorse 3D per il mondo mobile e il web, il punto di vista di un designer 3D in cui ha condiviso la sua esperienza e come è riuscito a ottimizzare la scena di Hill Valley da meno di 1 fps a 60 fps.
L'obiettivo iniziale di Sponza era costruire due scene. Uno per desktop e uno per dispositivi mobili con meno complessità, trame più piccole e mesh e geometrie più semplici. Ma durante i nostri test, abbiamo finalmente scoperto che la versione desktop funzionava abbastanza bene anche sui dispositivi mobili in quanto può funzionare fino a 60 fps su un iPhone 6s o un Android OnePlus 2. Abbiamo quindi deciso di non continuare a lavorare sulla versione mobile più semplice.
Ma ancora una volta, probabilmente sarebbe stato meglio avere un approccio mobile first pulito su Sponza per raggiungere i 30 fps+ su molti dispositivi mobili, quindi migliorare la scena per dispositivi mobili e desktop di fascia alta. Tuttavia, la maggior parte dei feedback che abbiamo ricevuto finora su Twitter sembra indicare che il risultato finale funziona molto bene sulla maggior parte dei dispositivi. Certo, Sponza è stato ottimizzato su una GPU HD4000 (Intel Core i5 integrata) che è più o meno equivalente alle attuali GPU dei cellulari di fascia alta.
Siamo rimasti abbastanza soddisfatti della prestazione che siamo riusciti a ottenere. Sponza utilizza il nostro shader con ambient , diffuse , bump , specular e reflection abilitati. Abbiamo alcune particelle per simulare piccoli fuochi su ogni angolo, ossa animate per le bandiere rosse, suoni posizionati in 3D e collisioni quando ti muovi usando la modalità interattiva.
Tecnicamente parlando, abbiamo 98 mesh utilizzate nella scena , che generano fino a 377781 vertici, 16 ossa attive, oltre 60 particelle che potrebbero generare fino a 36 draw call. Cosa abbiamo imparato? Una cosa è certa: avere un minor numero di draw call è la chiave per prestazioni ottimali, ancor di più sul web.
Il caricatore
Per Sponza, volevamo creare un nuovo caricatore, diverso da quello predefinito che stiamo utilizzando sul sito web BabylonJS, per avere un'app web pulita e raffinata. Ho quindi chiesto a Michel di suggerire qualcosa di nuovo.
Per prima cosa mi ha inviato la seguente schermata:
In effetti, lo schermo sembra molto bello quando lo guardi per la prima volta. Ma allora potresti iniziare a chiederti come lo farai funzionare su tutti i dispositivi, in modo davvero reattivo? Scopriamolo.
Parliamo prima dello sfondo. L'effetto sfocato creato da Michel era carino ma non funzionava bene su tutte le dimensioni e risoluzioni delle finestre generando un po' di moiré. L'ho quindi sostituito con uno screenshot "classico" della scena. Tuttavia, volevo che lo sfondo riempisse completamente lo schermo senza barre nere e senza allungare l'immagine per rompere il rapporto.
La soluzione deriva principalmente da CSS background-size: cover
+ centrando l'immagine sugli assi X e Y. Di conseguenza, abbiamo quindi l'esperienza che stavo cercando, qualunque sia il rapporto dello schermo utilizzato:
Le altre parti utilizzano il buon vecchio posizionamento CSS basato sulla percentuale. Ok, con quello ordinato, come gestiamo la tipografia: la dimensione del carattere dovrebbe essere basata sulla dimensione del viewport. Ovviamente, possiamo usare le unità viewport per questo. vw
e vh
(dove 1vw è l'1% della larghezza del viewport e 1vh è l'1% dell'altezza del viewport) sono supportati abbastanza bene tra i browser, in particolare in tutti i browser compatibili con WebGL. (C'è anche un articolo su Viewport Sized Typography su Smashing Magazine che ti consiglio vivamente di leggere.)
Infine, stiamo giocando con la proprietà opacity
dell'immagine di sfondo per spostarla da 0
a 1
in base al processo di download corrente passando da 0 a 100%.
Oh, a proposito, le animazioni del testo vengono semplicemente eseguite utilizzando transizioni CSS o animazioni combinate con un layout flexbox per avere un modo semplice ma efficiente per visualizzare al centro o in ogni angolo.
Gestire tutti gli input in modo trasparente
Il nostro motore WebGL sta facendo tutto il lavoro sul lato del rendering per visualizzare correttamente gli elementi visivi su tutte le piattaforme. Ma come possiamo garantire che l'utente sarà in grado di muoversi all'interno della scena qualunque sia il tipo di input utilizzato?
Nella versione precedente di Babylon.js, supportavamo tutti i tipi di input e interazioni dell'utente: tastiera/mouse, touch, joystick touch virtuali, gamepad, orientamento del dispositivo VR (per Card Board) e WebVR, ciascuno tramite una telecamera dedicata. Puoi leggere la nostra documentazione per saperne di più su di loro.
Il tocco viene gestito universalmente con la specifica Pointer Events propagata a tutte le piattaforme tramite jQuery PEP polyfill (generando eventi Touch per l'app quando necessario). Per saperne di più su Pointer Events, puoi leggere su Unifying touch and mouse: come Pointer Events renderà facile il supporto del tocco cross-browser
Torniamo quindi alla demo. L'idea per Sponza era quella di avere una telecamera unica, in grado di gestire tutti gli scenari degli utenti contemporaneamente: desktop, mobile e console.
Abbiamo finito per creare la UniversalCamera . Ad essere onesti, è stato così ovvio e semplice da creare che ancora non so perché non l'abbiamo fatto prima. La UniversalCamera è più o meno una fotocamera per gamepad che estende la TouchCamera che estende la FreeCamera .
La FreeCamera fornisce la logica tastiera/mouse; la TouchCamera fornisce la logica del tocco e l'estensione finale fornisce la logica del gamepad.
La UniversalCamera è ora utilizzata in Babylon.js per impostazione predefinita. Se stai sfogliando le demo, puoi spostarti all'interno delle scene utilizzando mouse, touch e gamepad su tutte. Ancora una volta, puoi studiare il codice per vedere come è fatto esattamente.
Sincronizzazione delle transizioni con la musica
Ora, questa parte è dove ci siamo posti la maggior parte delle domande. Potresti aver notato che la sequenza introduttiva è sincronizzata con aree specifiche della traccia musicale . Le prime righe vengono visualizzate quando alcuni dei tamburi entrano in azione e la sequenza finale finale passa rapidamente da una telecamera all'altra su ogni nota dello strumento a fiato che stiamo usando.
Sincronizzare l'audio con il ciclo di rendering WebGL non è facile. Ancora una volta, questa è la natura mono-thread di JavaScript che genera questa complessità. Gli articoli su Introduzione ai Web Workers HTML5: l'approccio multithreading JavaScript condividono alcune intuizioni alla base di ciò. È davvero importante capire il problema per comprendere il problema globale che stiamo affrontando, ma entrare nei dettagli qui esula dallo scopo di questo articolo.
Di solito, nelle scene demo (e nei videogiochi), se desideri sincronizzare le immagini con i suoni/musica, sarai guidato dallo stack audio. Spesso vengono utilizzati due approcci:
- Genera metadati che verrebbero iniettati nei file audio e che quindi potrebbero "richiamare" alcuni eventi quando raggiungi una parte specifica di esso,
- Analisi in tempo reale del flusso audio tramite FFT o tecnologie simili per rilevare picchi interessanti o modifiche BPM che genererebbero nuovamente eventi per il motore visivo.
Questi approcci funzionano particolarmente bene in ambienti multi-thread come C++. Ma in JavaScript, con Web Audio, abbiamo due problemi:
- JavaScript, che è mono-thread e, sfortunatamente, la maggior parte delle volte i web worker non ci aiutano davvero,
- Web Audio non ha eventi che potrebbero essere rispediti al thread dell'interfaccia utente anche se Web Audio viene gestito in un thread separato dal browser.
Web Audio ha un timer molto più preciso di JavaScript. Sarebbe stato fantastico poter utilizzare questo timer separato su un thread separato per riportare gli eventi al thread dell'interfaccia utente. Ma oggi non puoi farlo (ancora?).
Dall'altro lato, stiamo rendendo la scena usando WebGL e il metodo requestAnimationFrame
. Ciò significa che, nei "casi migliori", abbiamo un lasso di tempo di 16 ms. Se te ne manca uno, dovrai aspettare fino a 16 ms per poter agire sul fotogramma successivo per riflettere la sincronizzazione del suono (ad esempio per lanciare un effetto "dissolvenza al nero").
Stavo quindi pensando di iniettare la logica di sincronizzazione nel ciclo requestAnimationFrame
. Ho studiato il tempo trascorso dall'inizio della sequenza e ho esaminato l'opzione di regolare la visuale per reagire a un evento audio. La buona notizia è che l'audio Web renderà il suono qualunque cosa stia succedendo nel thread dell'interfaccia utente principale. Ad esempio, puoi essere sicuro che il timestamp di 12 secondi della musica arriverà esattamente 12 secondi dopo l'inizio della riproduzione della musica, anche se la GPU ha difficoltà a riprodurre la scena.
Alla fine, abbiamo finalmente scelto probabilmente la soluzione più semplice in assoluto: usare le chiamate setTimeout()
! Sì, lo so. Se guardi nella maggior parte degli articoli là fuori, incluso quello a cui ho collegato sopra, scoprirai che è piuttosto inaffidabile. Ma nel nostro caso, una volta che la scena è pronta per essere renderizzata, sappiamo di aver scaricato tutte le nostre risorse (texture e suoni) e compilato i nostri shader. Non dovremmo essere troppo infastiditi da eventi imprevisti che saturano il thread dell'interfaccia utente. GC potrebbe essere un problema , ma abbiamo anche impiegato molto tempo a combatterlo nel motore: riducendo la pressione sul garbage collector utilizzando la barra degli sviluppatori F12 di Internet Explorer 11.
Tuttavia, sappiamo che questa soluzione è tutt'altro che ideale. Passare a un'altra scheda o bloccare il telefono e sbloccarlo pochi secondi dopo potrebbe generare alcuni problemi nella parte di sincronizzazione della demo. Potremmo risolvere questi problemi usando l'API di visibilità della pagina, ad esempio mettendo in pausa il ciclo di rendering, vari suoni e ricalcolando i tempi successivi per le chiamate setTimeout()
.
Ma forse ci siamo persi qualcosa; forse, e anche probabilmente, c'era un approccio migliore per gestire questo problema. Ci piacerebbe sentire i tuoi pensieri e suggerimenti nella sezione commenti se pensi che ci sia un modo migliore per risolvere lo stesso problema.
Gestione dell'audio Web su iOS
L'ultima sfida che vorrei condividere con te è il modo in cui Web Audio viene gestito da iOS su iPhone e iPad. Se stai cercando articoli su "audio web + iOS", troverai molte persone che hanno difficoltà a riprodurre suoni su iOS. Ora, cosa sta succedendo lì?
iOS ha un notevole supporto per Web Audio, anche la modalità audio binaurale. Ma Apple ha deciso che una pagina Web non può riprodurre alcun suono per impostazione predefinita senza l'interazione di un utente specifico. Questa decisione è stata probabilmente presa per evitare che pubblicità o altro disturbasse l'utente riproducendo suoni non richiesti.
Cosa significa per gli sviluppatori web? Bene, devi prima sbloccare il contesto audio web di iOS dopo il tocco di un utente, prima di provare a riprodurre qualsiasi suono. In caso contrario, la tua applicazione Web rimarrà disperatamente muta.
Sfortunatamente, l'unico modo in cui ho trovato per eseguire questo controllo se eseguendo un approccio di sniffing della piattaforma utente poiché non ho trovato un modo di rilevamento delle funzionalità per farlo. Questa è a dir poco una tecnica orribile e non a prova di proiettile, ma non sono riuscito a trovare nessun'altra soluzione che risolvesse il problema. Il codice? Ecco qui!
Se non sei su iPad/iPhone/iPod, il contesto audio è immediatamente disponibile per l'uso. In caso contrario, sbloccheremo il contesto audio di iOS riproducendo un suono vuoto generato dal codice sull'evento touchend . Puoi registrarti all'evento onAudioUnlocked se desideri attendere prima di avviare il gioco. Quindi, se stai avviando Sponza su un iPhone/iPad, avrai questa schermata finale alla fine della sequenza di caricamento:
Toccando un punto qualsiasi dello schermo sbloccherà lo stack audio di iOS e avvierà lo "show".
Quindi eccoci qui! Spero che ti siano piaciute alcune informazioni sullo sviluppo della demo. Per saperne di più, leggi il codice sorgente completo di questa demo sul nostro GitHub. Ovviamente, tutto è open source e puoi trovare i file principali su GitHub: index.js e babylon.demo.ts.
Infine, spero davvero che ora sarai ancora più convinto che il web sia sicuramente un'ottima piattaforma per i giochi! Restate sintonizzati, poiché stiamo lavorando a nuove demo proprio in questo momento, e speriamo che anche loro saranno piuttosto impressionanti.
Questo articolo fa parte della serie di sviluppo Web di esperti e ingegneri della tecnologia Microsoft sull'apprendimento pratico di JavaScript, sui progetti open source e sulle best practice per l'interoperabilità, incluso il browser Microsoft Edge.Ti invitiamo a eseguire test su browser e dispositivi, incluso Microsoft Edge, il browser predefinito per Windows 10, con strumenti gratuiti su dev.microsoftedge.com, inclusi gli strumenti per sviluppatori F12, sette strumenti distinti e completamente documentati per aiutarti a eseguire il debug, il test e velocizza le tue pagine web Inoltre, visita il blog di Edge per rimanere aggiornato e informato da sviluppatori ed esperti Microsoft.