WebVR con A-Frame

WebVR è una nuova specifica del World Wide Web Consortium, di cui potete leggere lo stato dei lavori su GitHub. Essendo una specifica ancora in fase di pre-release è supportata ad oggi solo da Edge e Chrome per Android, come potete controllare su caniuse.com. In pratica tramite WebVR è possibile creare delle scene in VR all’interno di applicazioni web compatibili con i vari device dedicati, dall’Oculus Rift fino a delle semplici Google Cardboard.

Anche se la specifica è lontana dall’implementazione su tutti i browser, in realtà è già possibile da oggi sviluppare applicazioni VR su Web grazie ad un polyfill creato dal team di Google. Inoltre Mozilla Labs sta lavorando attivamente a A-Frame: un framework di alto livello per la creazione di applicazioni VR sul web.

La nostra prima applicazione A-Frame

Creiamo la nostra prima applicazione A-Frame. Per semplicità sarà una scena statica, composta da un cubo rosso e una luce direzionale.

<html>
  <body>
    <a-scene>
      <a-light 
        type="directional" 
        color="white" 
        intensity="2" 
        position="-5 5 2">
      </a-light>
      <a-box 
        color="red" 
        width="1"
        height="1" 
        position="-2 2 -3"></a-box>
      <a-sky color="#009DCD"></a-sky>
    </a-scene>
  </body>
</html>

Il risultato è visibile in questo codepen. Per passare alla modalità VR, una volta aperta la pen sul sito ufficiale cliccando su “Edit non Codepen”, basta cliccare sull’icona a forma di visore in basso sulla destra su un dispositivo mobile. Dopodiché basta inserire il vostro dispositivo in una Cardboard o un qualsiasi visore VR per smartphone.

See the Pen Static A-Frame scene by Francesco Strazzullo (@francesco-strazzullo) on CodePen.

Già grazie a questo semplicissimo esempio possiamo notare una delle feature principali di A-Frame, ossia il fatto di essere dichiarativo e basato su componenti. Chi di voi ha già lavorato con Angular o React probabilmente si troverà a suo agio. A-Frame possiede out of the box moltissimi componenti (chiamati anche entità) come sfere, cubi e altre forme geometriche.

Creazione di entità custom

Tramite la funzione AFRAME.registerComponent possiamo creare dei nuovi componenti. Quello che stiamo per vedere è il codice di un componente che modifica il comportamento di un’entità esistente. Il nostro componente si occupa di spostare un elemento sullo schermo in base a dei valori che riceve in ingresso.

AFRAME.registerComponent('move-around', {
  schema: {
    moveX: {type: 'number', default: 0},
    moveY: {type: 'number', default: 0},
    moveZ: {type: 'number', default: 0}
  },
  init: function () {
    this.counter = 0
    this.originalPosition = this.el.getAttribute('position')
  },
  tick: function (time, timeDelta) {
    this.counter += (0.002 * timeDelta)

    let {x, y, z} = this.originalPosition

    let newX = x + this.data.moveX * Math.cos(this.counter)
    let newY = y + this.data.moveY * Math.cos(this.counter)
    let newZ = z + this.data.moveZ * Math.cos(this.counter)

    this.el.setAttribute('position', { x: newX, y: newY, z: newZ })
  }
})

Analizziamo insieme gli elementi del componente appena creato. All’interno della proprietà schema definiamo quali sono gli attributi che accettiamo in ingresso quando questo componente viene utilizzato. Possiamo definirne tipo e valore di default, con una gestione che ricorda molto da vicino le PropTypes di React. init è una funzione di inizializzazione che viene invocata una sola volta durante tutto il lifecycle del componente. Infine tick viene invocata ogni volta che è possibile fare un nuovo render, ed è proprio qui che avviene lo spostamento dell’elemento modificando l’attributo position.

Possiamo utilizzare il componente move-around come qualsiasi altro attributo HTML.

<html>
  <body>
    <a-scene>
      <a-plane 
        position="0 0 0" 
        rotation="-90 0 0" 
        width="100" 
        height="100" 
        color="#0080AF">
      </a-plane>
      <a-sphere 
        move-around="moveX: 10; moveZ: -5" 
        position="2 1 -5" 
        radius="1" 
        color="#CDCC00">
      </a-sphere>
      <a-box 
        move-around="moveY: 1" 
        position="-2 1 -5" 
        width="2" 
        height="2" 
        color="#CDCC00">
      </a-sphere>
    </a-scene>
  </body>
</html>

Notiamo come i parametri di move-around vengono scritti nella stessa sintassi di uno stile CSS in linea. Il risultato del codice appena visto è presente nel prossimo codepen.

See the Pen Custom Component A-Frame scene by Francesco Strazzullo (@francesco-strazzullo) on CodePen.

Gestione Assets

Come già detto in uno dei miei ultimi post su PixiJS il caricamento degli asset è una parte fondamentale di ogni libreria che permette di creare applicazioni ad alto contenuto “grafico”. Anche questo aspetto di A-Frame è dichiarativo come possiamo vedere con il prossimo esempio

<html>
<body>
  <a-scene>
    <a-assets>
      <a-asset-item id="lego-model" src="https://francesco-strazzullo.github.io/a-frame-examples/assets/lego.obj"/>
      <a-asset-item id="logo" src="https://francesco-strazzullo.github.io/a-frame-examples/assets/logo.jpg" />
      <a-asset-item id="background" src="https://upload.wikimedia.org/wikipedia/commons/8/89/Langkawi_Sky_Bridge_Photosphere.jpg" />
    </a-assets>
    <a-obj-model 
      src="#lego-model" 
      material="color: red" 
      position="2 1 -5" 
      scale="0.05 0.05 0.05">
    </a-obj-model>
    <a-box 
      position="-2 1 -5" 
      width="2" 
      height="2" 
      material="src: #logo">
    </a-box>
    <a-sky  material="src: #background"></a-sky> 
  </a-scene>
</body>
</html>

Gli asset vengono tutti caricati attraverso il componente a-asset-item. Per quanto riguarda le immagini che vogliamo applicare come sfondo delle nostre entità dobbiamo utilizzare l’attributo material. Ma le immagini non sono gli unici asset che è possibile gestire. Notiamo infatti che uno degli asset che carichiamo è un file obj che contiene un modello 3D di una minifigure LEGO (che è possibile scaricare su free3d.com).

See the Pen Asset A-Frame scene by Francesco Strazzullo (@francesco-strazzullo) on CodePen.

Interazione con l’utente

A-Frame contiene al suo interno alcuni componenti come hand-controls che possono interagire con i controller dei sistemi VR più “completi” come Oculus Rift oppure HTC Vive. Ma come possiamo interagire con l’utente quando possiede un device più semplice che non comprende anche un controller? A-Frame pensa anche a questo: se l’utente “guarda” ad un elemento per più di un paio di secondi, su quell’elemento viene emesso un evento “click” che possiamo intercettare. In questo modo possiamo quindi creare delle applicazioni interattive senza dover richiedere all’utente una spesa eccessiva. Nel prossimo esempio vedremo un photo wall con una mia foto e quelle di tutti in miei colleghi di extrategy. Osservando una delle foto comparirà sullo schermo il motto personale di ognuno.

<html>
<body>
  <a-scene>
    <a-assets>
      <img crossorigin="anonymous" id="strazzullo" src="https://francesco-strazzullo.github.io/a-frame-examples/assets/francesco-strazzullo.jpg">
      <!-- Other Assets -->
    </a-assets>
    <a-sky color="#0080AF"></a-sky>
    <a-plane exer="strazzullo"></a-plane>
    <!-- Other Photos -->
    <a-entity camera look-controls>
      <a-cursor 
          fuse="true" 
          raycaster="objects: [exer]">
      </a-cursor>
    </a-entity>
    <a-text 
       role="message" 
       text="value: Look at one of the exer"              
       position="0 -2 -4">
    </a-text>
  </a-scene>
</body>
</html>

Per interagire con gli elementi utilizziamo il componente raycaster. Questo componente reagisce con tutti gli elementi che intersecano una linea compresa tra se stesso e l’infinito. Se utilizziamo raycaster con l’entità a-cursor interagiremo con gli elementi che l’utente sta “guardando”. Il valore dell’attributo raycaster non è niente altro che un selettore, per interagire solo con gli elementi che ci interessano. In questo casto con tutti gli elementi che hanno l’attributo exer: componente custom di cui possiamo leggere qui il codice:

let index = 0
const onExerClick = exer => {
    const codingTo = `coding to ${CODING_TOS[exer]}`
    document.querySelectorAll('[role="message"]').forEach(message => {
    const textAttribute = message.getAttribute('text')
    const newTextAttribute = Object.assign({}, textAttribute, {value: codingTo})
    message.setAttribute('text', newTextAttribute)
  })
}
AFRAME.registerComponent('exer', {
  schema: {type: 'string'},
  init: function () {
    const x = START_X + ((WIDTH + SPACING) * (index % ELEMENTS_IN_A_ROW))
    const y = START_Y - ((WIDTH + SPACING) * Math.floor(index / ELEMENTS_IN_A_ROW))
    this.el.setAttribute('position', {x, y, z: Z})
    this.el.setAttribute('width', WIDTH)
    this.el.setAttribute('height', WIDTH)
    this.el.setAttribute('material', {src: `#${this.data}`})
    this.el.addEventListener('click', () => { onExerClick(this.data) })
    index++
  }
})

Come possiamo facilmente vedere, una volta utilizzato raycaster, sull’oggetto “osservato” viene emesso un semplice evento click. A questo punto ci basta una banale addEventListener per rispondere all’evento. Il codice completo e un esempio live è presente in questa ultima codepen.

See the Pen Interactive A-Frame scene by Francesco Strazzullo (@francesco-strazzullo) on CodePen.

Conclusioni

Per tutti i frontend engineer la VR è un qualcosa da tenere d’occhio, l’acquisto da parte di Facebook di Oculus è un chiaro segnale delle intenzioni dell’azienda di Zuckenberg. Se a questo aggiungiamo l’uscita del framework React VR e l’annuncio di un visore stand-alone del prezzo di 200$ da parte di Oculus per il 2018 possiamo supporre un futuro roseo per la VR. E secondo chi vi scrive A-Frame è un ottimo modo per iniziare a giocarci per capirne le potenzialità. Il suo essere già basato a componenti e la disarmante semplicità lo rendono davvero una tecnologia da tenere d’occhio. Se volete sperimentare trovate il progetto funzionante con il codice completo dell’ultimo esempio su Github. Alla prossima.

Francesco Strazzullo

Faccio il Front-end engineer per extrategy dove mi occupo di applicazioni Angular, React e applicazioni mobile. Da sempre sono appassionato di Architetture software e cerco di applicarle in maniera innovativa allo sviluppo frontend in generale.