A-Frame est un framework web open-source dédié au développement d'expériences en réalité virtuelle et augmentée. Basé sur HTML et Three.js, il permet facilement de créer du contenu en VR.
Afin que vous puissiez suivre les exercices proposés au fur et à mesure, clonez (ou forkez) le repo boilerplate pour Vue et A-Frame.
A-Frame adopte une architecture Entity-Component-System. Ce modèle se compose des 3 éléments suivants :
-
Entité : conteneur permettant d'accueillir des composants. Sans composant, les entités ne sont pas rendues, à l'image de
<div>
vides. -
Composant : module réutilisable attachable à des entités, pour leur donner une apparence, un comportement ou une fonctionnalité.
-
Système : module qui permet de gérer des données globales, des services et des classes pour les composants. Si souhaité, il permet également de séparer la logique (système) des données (composant).
Si l'on prend pour exemple un smartphone comme entité, ses composants définiront son apparence (forme, couleur), son comportement (sonner quand on reçoit un appel) et ses fonctionnalités (appel, appareil photo). Le dispositif peut également comprendre un système pour la gestion des comportements liés aux appels.
Plus concrétement, ces éléments sont intégrés comme suit dans A-Frame.
La syntaxe de base d'une entité s'apparente à une balise HTML et s'utilise comme telle. Les entités sont donc placées dans le body
(ou ailleurs, selon le framework utilisé) :
<a-entity></a-entity>
Il existe cependant des éléments de base préconstruits, appelés primitives, qui permettent de construire facilement une scène sans devoir plonger dans l'architecture sous-jacente. On retrouve notamment des éléments tels que <a-box>
, <a-sphere>
ou même <a-sky>
.
Consultez la doc pour parcourir les primitives existantes (en bas du menu) ou créer des primitives personnalisées.
Une scène est construite avec l'entité <a-scene>
. Elle constitue l'élément racine de toute entité. Elle comprend une caméra <a-camera>
et des lumières <a-light>
par défaut, qui sont remplacés si l'on déclare explicitement ces primitives. Dans le boilerplate, une scène a déjà été créée dans le composant Vue TheScene
.
Insérer une primitive
<a-box>
dans votre scène (TheScene).Note : Sur un ordinateur, déplacez-vous avec les touches
wasd
pour voir le cube.
Les composants sont attachés aux entités comme des attributs sur des éléments HTML. Ils peuvent accueillir des données personnalisées sous forme de propriétés.
<a-box position="0 0 -3"></a-box>
Les propriétés multiples sont passées avec la syntaxe suivante :
<a-entity box="width: 2; height: 3"></a-entity>
Le positionnement des entités se fait à partir d'un système de coordonnée basé sur la main droite. Les valeurs positives vont donc à droite (X), en haut (Y) et contre nous (Z). Les unités sont en mètre.
Une entité placée à l'intérieur d'une autre hérite de ses transformations (position, scale, rotation). Dans l'exemple suivant, la sphère aura la même position, la même rotation et la même échelle que la boîte parente.
<a-box position="0 1.5 -3" rotation="0 45 45" scale="1 2 1">
<a-sphere color="red"></a-sphere>
</a-box>
Ce système d'héritage requiert ainsi de placer la caméra et les mains dans une sorte de support (camera rig), une entité parente, qui assurera la cohérence dans les déplacements et rotation de ces éléments. Dans le boilerplate, un support caméra a déjà été créé, assurant le contrôle des mains et de la caméra pour les différents types d'appareils (ordinateur, smartphone, casque VR).
Modifiez la position de la box placée précédemment pour qu'elle apparaisse devant vous (au chargement). Ajoutez lui également une couleur de votre choix.
Pour vous faire un peu plus la main avec ces entités, créer une bande de guidage PMR (line) qui sillonne entre 3 bâtiments (box).
Comme pour les primitives, il est possible de créer nous-même des composants. On utilise alors la fonction registerComponent
.
AFRAME.registerComponent('log', {
schema: {
message: {
type: 'string',
default: 'Hello, World!'
}
},
init: function () {
console.log(this.data.message)
},
update: function (oldData) {...}
})
Une fois enregistré le composant peut être utilisé comme suit.
<a-entity log="message: Hello everyone!"></a-entity>
On utilise un schema
pour enregistrer des propriétés qui peuvent être transmise par l'entité. Dans le composant, on accède aux propriétés via this.data
.
La fonction init
est appelée une seule fois, lors de l'initialisation du composant. Si on veut changer dynamiquement les propriétés passées, on peut mettre à jour le composant avec la fonction update
.
Les composants offrent également d'autres fonctions utilitaires, comme tick()
, play()
ou pause()
.
Tip
Pas besoin de réinventer la roue ! Il existe de multiples composants natifs à A-Frame et bien d'autres ont déjà été créés par la communauté, comme a-frame-extras et physx.
Créer un nouveau composant nommé
clickable
dans le dossiersrc/aframe
. Celui-ci permettera d'indiquer visuellement si un élément peut être "cliqué".Dans
schema
, ajouter une propriétécolor
de typecolor
et la couleur par défaut de votre choix (différente de celle du cube).Avant de continuer, il faut comprendre comment fonctionne les interactions et les événements dans A-Frame.
Quand on manipule les différentes entités, il est préférable de le faire dans un composant. Comme A-Frame est basé sur HTML, on peut accéder aux entités en utilisant des méthode de sélection comme .querySelector()
par exemple.
Tip
À l'intérieur d'un composant, on a accès directement à l'entité liée via this.el
et à la scène via this.el.sceneEl
.
let boxes = this.el.sceneEl.querySelectorAll('a-box');
Pour récupérer les valeurs des propriétés d'un composant lié à une entité, on utilise la méthode .getAttribute()
.
el.getAttribute('width')
Pour attacher un composant à une entité ou le mettre à jour, on utilise la méthode .setAttribute()
.
el.setAttribute('geometry', {
primitive: 'box',
height: 3,
width: 1
});
Important
Pour des raisons de performances, il est préférable de mettre à jour les valeurs de position, rotation, échelle (scale) et visibilité (visible) via three.js directement. On peut accéder à l'objet 3D sous-jacent via el.object3D
.
el.object3D.position.x += 5
En ce qui concerne les événements, on peut en émettre avec la fonction .emit()
et en écouter avec addEventListener()
.
el.emit('physicscollided', {collidingEntity: anotherEl}, false);
el.addEventListener('physicscollided', event => {
console.log('Entity collided with', event.detail.collidingEntity);
});
Reprenons notre composant
clickable
. Nous allons créer 2 fonctionsonEnter
etonLeave
, qui seront respectivement appelées lors des événementsmouseenter
etmouseleave
. Dans la fonctioninit
, nous allons lier (bind) les événements au composant pour pouvoir utiliserthis
. Cela peut être fait ainsi (à faire également pour onLeave) :
this.onEnter = this.onEnter.bind(this);
this.el.addEventListener('mouseenter', this.onEnter);
Dans la fonction onEnter, on va vérifier si on utilise le raycaster ou le curseur (VR ou 3D) pour ensuite changer la couleur.
onEnter: function (evt) {
const cursor = evt.detail.cursorEl;
if (cursor.getAttribute('raycaster').showLine) {
this.savedColor = cursor.getAttribute('raycaster').lineColor;
cursor.setAttribute('raycaster', 'lineColor', this.data.color);
} else {
this.savedColor = cursor.getAttribute('material').color;
cursor.setAttribute('material', 'color', this.data.color);
}
},
En se basant sur la fonction
onEnter
, écrivez le contenu deonLeave
pour revenir à la couleur de base.Enfin, pour des raisons de performences, on supprime les
eventListeners
lorsque l'entité est enlevée de la scène.
remove: function () {
this.el.removeEventListener('mouseenter', this.onEnter);
this.el.removeEventListener('mouseleave', this.onLeave);
},
Vous pouvez tester votre composant en l'attachant au cube présent dans votre scène.
On peut accéder à un système via la scène :
document.querySelector('a-scene').systems[systemName];
Un système est enregistré de façon similaire à un composant. Si le nom du système correspond au nom du composant, on peut y accéder directement depuis le composant via this.system
.
AFRAME.registerSystem('camera', {
schema: {},
init: function () {},
...
});
AFRAME.registerComponent('camera', {
schema: {...},
init: function () {
console.log(this.system)
},
...
});
A-Frame offre la possibilité de charger différents assets, comme du son, des images, des vidéos, des matériaux et des modèles 3D.
Le framework possède un système de gestion d'assets, qui permet de regrouper, pré-charger et mettre en cache les assets, ce qui assure de meilleures performance.
Afin d'utiliser ce système, les assets sont placés dans une entité <a-assets>
comme dans l'exemple suivant.
<a-scene>
<!-- Système de gestion d'assets -->
<a-assets>
<!-- Modèle 3D au format GLTF -->
<a-asset-item id="horse-gltf" src="horse.gltf"></a-asset-item>
<audio id="neigh" src="neigh.mp3"></audio>
<img id="advertisement" src="ad.png">
<video id="kentucky-derby" src="derby.mp4"></video>
</a-assets>
<!-- Scene -->
<a-gltf-model src="#horse-gltf"></a-gltf-model>
<a-sound src="#neigh"></a-sound>
<a-plane src="#advertisement"></a-plane>
<a-entity geometry="primitive: plane" material="src: #kentucky-derby"></a-entity>
</a-scene>
A-Frame offre un outil de visualisation, qui permet de voir sa scène sous d'autres angles et de faciliter ainsi le développement. Il peut être ouvert via la commande ctrl + alt + i
.
Chapitre précédant : Introduction à la VR
Chapitre suivant : Exercice pratique VR