Il mondo è 3D, ma ci hanno tolto una dimensione. Alla scoperta di React 360, framework VR per la creazione di UI immersive.
Questo articolo riprende il post originale Road to our world. Step #1: React 360, pubblicato su Medium il 16 marzo 2020.
Ci sono diverse tecnologie che ci fanno presagire come sarà il nostro futuro. Tra intelligenza artificiale, blockchain e realtà virtuale, quest’ultima ha sempre suscitato in me più di un interesse, facendomi sognare. A volte sono scene e mondi che replicano fedelmente la realtà, per condividere qualcosa di conosciuto e trasportarlo nel mondo virtuale da godere ovunque ci si trovi. Altre volte, invece, questi mondi ci permettono di creare qualcosa che sarebbe impossibile nella vita reale. Con la VR, quindi, l’unico nostro limite è veramente la fantasia.
Eccomi qui, quindi, ad esplorare diverse tecnologie che possono permettermi di mettere in pratica ciò che ho sempre immaginato.
Parlo di visite virtuali in musei che magari non potrò mai vedere dal vivo, oppure di visite guidate nella nostra prossima abitazione, o semplicemente, di un album fotografico delle nostre ultime vacanze (coronavirus permettendo).
In questo percorso andrò a esplorare tecnologie nate appositamente per il web e fruibili, in alcuni casi, anche attraverso Headset per la realtà virtuale, come Google Cardboard, Oculus, HTC Vive, ecc.
Le tecnologie/librerie che permettono l’implementazione di scene 3D sul web sono molteplici. Ognuna di esse si porta dietro vantaggi e svantaggi. Alcune sono già pronte per le nuove specifiche WebXR che sostituiranno WebVR e WebAR, raggruppandole in un’unico set di API in grado di fornire gli strumenti per lo sviluppo di applicazioni per la realtà virtuale e realtà aumentata.
Le tecnologie di cui parlo sono:
- React 360
- a-frame
- three.js
Una menzione particolare merita anche Unity, che permette di creare applicazioni e giochi 2D/3D per il web, mobile, desktop ecc.
Introduciamo React 360
Tra le tecnologie abilitanti in questo senso, un ruolo non secondario è ricoperto da React 360, il framework a cui è dedicato il presente articolo. Ruolo non secondario, dico, perché dietro a React 360 c’è Oculus (dici Oculus, ma leggi Facebook) e dietro Oculus ci sono le tecnologie attualmente allo stato dell’arte in fatto di realtà virtuale (e presto realtà aumentata).
La definizione, sul sito ufficiale è cristallina:
React 360 è un framework per la creazione di interfacce utente 3D e VR. Costruito su React, una libreria progettata per semplificare la creazione di UI complesse, React 360 consente di utilizzare strumenti e concetti familiari per creare contenuti immersivi a 360 ° sul Web.
Una cosa fondamentale su cui porre l’accento è proprio la natura “conosciuta” di React 360. Il framework si basa infatti su React e React Native, da cui prende, tra le altre cose, la gestione del Runtime e la creazione di Moduli per poter “parlare” con la nostra React App.
Potrebbero interessarti anche:
React Native, quando e perché è meglio di Ionic
React Native e Flutter, dalle librerie di prima generazione ai framework per le app semi-native
Scaffolding del nostro primo progetto
Per prima cosa installiamo la CLI che ci servirà per creare la nostra prima applicazione, tenendo in considerazione le versioni minime necessarie di node, npm e yarn, che dovranno essere:
- Node.js versione 6.0.0 o maggiore.
- yarn o npm (>= v3.0.0).
npm install -g react-360-cli
oppure, per chi lo preferisce, è possibile utilizzare yarn:
yarn global add react-360-cli
A questo punto possiamo creare la nostra prima applicazione React 360, scrivendo:
react-360 init NOME-PROGETTO
Come per ogni altro progetto React, a questo punto possiamo scrivere:
> cd
NOME-PROGETTO
> npm start
Aprendo il browser, appena lanciato l’URL http://localhost:8081/index.hml, vedremo (dopo un po’ di attesa) questa immagine:
Due environment, stesso obiettivo
Se ci soffermiamo sul terminale in cui abbiamo fatto partire la nostra applicazione, ci accorgiamo di una cosa:
L’applicazione ci ha messo un pochino a partire, perché ha caricato due javascript: client.js ed index.js in quest’ordine (che non è casuale). Tali file, insieme a index.html, sono più importanti fra quelli che costituiscono la nostra applicazione.
Come è facile intuire, index.html è il punto in cui andremo a registrare la nostra applicazione. Da notare che possiamo includere dei parametri alla nostra applicazione, come la posizione dei nostri assets (immagini, video, modelli 3D, audio), per potervi accedere facilmente dall’applicazione stessa.
<html>
<head>
<title>welcomeReact360</title>
<style>body { margin: 0;
}</style>
<meta name=”viewport”
content=”width=device-width, initial-scale=1, user-scalable=no”>
</head>
<body>
<!– Attachment point for your app
–>
<div id=”container“></div>
<script src=”./client.bundle?platform=vr“></script>
<script>
// Initialize the React 360
application
React360.init(
‘index.bundle?platform=vr&dev=true‘,
document.getElementById(‘container‘),
{
assetRoot: ‘static_assets/’,
}
);
</script>
</body>
</html>
Gli altri due file di nostro interesse, quindi, sono client.js, che si occuperà di fare il rendering della nostra applicazione, e index.js, da dove inizieremo a implementare la nostra applicazione vera e propria.
Perché questa suddivisione?
Come vedremo, a partire da index.js potremo scrivere la nostra applicazione, cioè fornire dati, applicare stili e definire componenti, che grazie a client.js verranno poi renderizzati in elementi 3D sui nostri schermi.
client.js viene anche chiamato Runtime ed esiste per lo stesso motivo per cui esiste in React Native, da cui prende le fila. Poiché i browser sono single thread, non possiamo permettere di perdere cicli di “renderizzazione” per l’utente, perché in un mondo immersivo, utilizzabile, per esempio, anche con Headset, glitch e lag, potrebbero questi causare problemi nella fruizione dei contenuti.
Eseguendo la nostra app in un contesto separato, permettiamo un frame rate elevato per i cicli di rendering, di cui necessita la nostra applicazione per una fruizione ottimale.
Bisogna tenere a mente un semplice principio:
- Scriviamo il codice della nostra applicazione.
- Questo viene fornito in input al Runtime che lo aggiunge alla nostra scena 3D.
- Quando l’utente fornisce un input, questo viene catturato dal Runtime e viene restituito verso la nostra applicazione come un evento.
- Sarà anche possibile aggiungere nuove funzionalità “non standard” al nostro Runtime, per passare ulteriori dati tra i due sistemi, utilizzando i cosiddetti moduli nativi (di cui parleremo in un prossimo articolo).
Questo roundtrip tra la parte Runtime e la nostra applicazione è tenuto in piedi dagli executor che si preoccupano di far girare, nel caso di una applicazione sviluppata in React 360, la nostra applicazione in un processo separato rispetto al runtime.
Nel caso specifico, l’executor di default della nostra applicazione sarà un web web worker, molto conosciuto per le applicazioni progressive, conosciute anche come PWA, che permettono di eseguire una serie di funzioni al di fuori del contesto principale del browser, come cashing e così via.
La nostra applicazione, quindi, girerà in un web worker al di fuori del contesto window principale del browser. È per questo che non avremmo, per esempio, accesso all’oggetto window.location messo a disposizione dal browser. Ma potremmo comunque usare moduli nativi che ci permettono, per esempio, di accedere alla history e alla location.
React App
Come accennato, l’applicazione di cui abbiamo appena fatto lo scaffolding, ci mette a disposizione un template per la nostra app, in modo da darci un punto di partenza da cui partire, customizzare ed espandere.
Il file index.js, quindi, conterrà i testi, i bottoni, le immagini che ci serviranno per creare la nostra applicazione
import React
from ‘react’;
import {
AppRegistry,
StyleSheet,
Text,
View,
} from ‘react-360’;export default class welcomeReact360
extends React.Component { render() {
return (
<View style={styles.panel}>
<View style={styles.greetingBox}>
<Text style={styles.greeting}>
Welcome to React360
</Text>
</View>
</View>
);
}
};const styles = StyleSheet.create({
panel: { // Fill the entire surface
width: 1000,
height: 600,
backgroundColor: ‘rgba(255, 255,
255, 0.4)’,
justifyContent: ‘center’,
alignItems: ‘center’,
},
greetingBox: {
padding: 20,
backgroundColor: ‘#000000’,
borderColor: ‘#639dda’,
borderWidth: 2,
},
greeting: {
fontSize: 30,
},
});AppRegistry.registerComponent(‘welcomeReact360’, () =>
welcomeReact360);
Come si evince dal listato precedente, quindi, la nostra applicazione altro non è che un React.Component, nel quale implementiamo la funzione render() per permettere di visualizzare quanto desiderato. Nello specifico, nell’esempio, possiamo vedere l’utilizzo del componente View, la quale altro non è che un contenitore che supporta il layout con Flexbox, la definizione dello stile e gli eventi di input. Nel 2D, il componente View definisce un box (lo possiamo pensare come un <div>), che possiamo usare per definire lo spazio tra gli elementi in esso contenuti, per cambiare il background e per creare dei bordi. Nel 3D possiamo utilizzare tale componente per raggruppare Entity o altre View o per fare trasformazioni nello spazio 3D.
L’ultima istruzione nel listato precedente rappresenta il punto di raccordo tra la nostra applicazione ed il Runtime a cui delegheremo il compito di renderizzarla.
AppRegistry.registerComponent(‘welcomeReact360’, () => welcomeReact360)
Runtime
Dopo aver registrato il nostro componente, nel client.js avremo accesso ad esso tramite il nome univoco che abbiamo scelto con l’istruzione precedente (nel nostro caso ‘welcomeReact360’). In questo modo potremo renderizzarlo nello spazio 3D grazie alle cosiddette Surface che rappresentano, appunto, delle superfici 3D ( Cylinder ), oppure 2D ( Flat ), in cui vivranno i nostri componenti definiti in index.js.
Surface
Le Surface ci permettono di aggiungere interfacce 2D nel mondo 3D, in un sistema di misurazione in pixel, anziché in dimensioni fisiche (a-frame, per esempio, definisce le distanze in metri).
Vediamo più in dettaglio cosa sono, analizzando il file client.js generato allo scaffolding dell’applicazione
import
{ReactInstance} from ‘react-360-web’;function init(bundle, parent,
options = {}) {
const r360 = new ReactInstance(bundle,
parent, {
fullScreen: true,
…options,
}); // Render your app content to the default
cylinder surface
r360.renderToSurface(
r360.createRoot(‘welcomeReact360’,
{ /* initial props */ }),
r360.getDefaultSurface()
);
// Load the initial environment
r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg’));
}window.React360 = {init};
Come vediamo dal codice scritto sopra, il nostro Runtime viene inizializzato tramite, appunto, la funzione init() che prende tre parametri in ingresso e crea un’istanza di ReactInstance il cui costruttore ha la seguente firma:
constructor(bundle: string,
parent: HTMLElement,
options: React360Options =
{})
che, possiamo notare, essere inizializzati tramite il file index.html, visto in precedenza, in cui, nello specifico abbiamo scritto:
…
React360.init(
‘index.bundle?platform=vr&dev=true‘,
document.getElementById(‘container‘),
{
assetRoot: ‘static_assets/’,
}
);
…
L’istruzione successiva permette di chiudere il cerchio con la App React che abbiamo implementato in precedenza nel file index.js. Infatti, come vediamo, chiamiamo la funzione renderToSurface()
renderToSurface(root: Root, surface: Surface, surfaceName?: string): number | null
che ha il compito di creare la nostra Surface di default e di renderizzarci sopra la nostra applicazione React.
Nella sezione del nostro file index.js avevamo visto che l’ultima istruzione ci ha permesso di registrare il nostro componente col nome “welcomeReact360”. In questo contesto, quindi, definiamo chi sarà a renderizzare tale componente e, nel nostro caso, sarà la Surface di default (di tipo cilindrica). Tale superficie verrà inizializzataa da due funzioni:
r360.createRoot(‘welcomeReact360’, { /* initial props */ }),
che non fa nient’altro che create un oggetto chiamato welcomeReact360 e con le proprietà iniziali (qualora ci fossero), definite nel commento come /* initial props */
La firma di tale funzione è semplicemente:
/*
* New API for creating a root component,
designed for mounting with
* renderToSurface or renderToLocation
commands.
* For now, it just returns a simple
object designed to encapsulate
* the information necessary for
mounting.
*/
createRoot(name:
string, initialProps: Object = {}): Root {
return {
name,
initialProps,
};
}
L’altro parametro ci permette di definire la surface che ci permetterà di presentare il nostro componente. Nel caso in esame, visualizziamo direttamente la surface di default, ottenibile tramite la chiamata alla funzione:
r360.getDefaultSurface()
La cui firma ci fa intuire quale sarà l’output di tale funzione, ovvero un oggetto di tipo Surface:
/**
* Returns the rendering system’s default 2D surface.
*/
getDefaultSurface(): Surface
Conclusioni
Concludiamo qui questa prima parte alla scoperta degli strumenti che ci permettono di sviluppare applicazioni immersive in ambienti 3D. Abbiamo scalfito soltanto la superficie di tale mondo, cercando di trasferire la passione e la bellezza e soprattutto, le potenzialità future, di questo tipo di User Experience che, sono sicuro, in futuro prenderanno sempre più piede e che mi auguro aiutino a fruire di contenuti in modo più naturale ed immersivo possibile.