A-Frame is a web framework that allows you to create various applications, games, scenes in virtual reality (VR). All of the above will be available directly from the browser of your VR helmet. This tool will be useful both for those who want to engage in the development of VR games in the browser, and for example, it can be useful as a platform for creating web BP applications, sites, landing pages. The use of BP Web is limited only by your imagination. Offhand, I can bring a couple of areas of human activity where BP can be useful: education, medicine, sports, sales, recreation.
What's inside?
A-Frame is not written from 0 on pure WebGL, it is based on the
Three.js library. Therefore, I recommend to start with the basic concepts of Three.js before starting to work with A-Frame, although this is not necessary, since A-Frame is designed in such a way that you least think about rendering geometry and concentrate more on the logic of your application . That's what the framework is for.
To do this, A-Frame postulates three main points, which we will discuss later.
A-Frame works with HTML
Many of the basic elements of A-Frame, such as scene, camera, box, sphere, etc., are added to the scene via tags of the same name with the prefix
a- . Each such element is registered as user. Today, A-Frame (0.8.0) uses the v0 specification.
<a-scene> <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box> <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere> </a-scene>
This small code fragment will draw a WebGL scene to which two objects will be added: a cube and a sphere with the specified parameters. In addition to the above two elements, there are a number of other primitives that can be added to the scene in the same way:
<a-circle>, <a-cone>, <a-cylinder>, <a-dodecahedron>, <a-icosahedron> , <a-octahedron>, <a-plane>, <a-ring>, <a-tetrahedron>, <a-torus-knot>, <a-torus>, <a-triangle> . Also in A-Frame there are a number of other elements that perform certain functions:
- <a-camera> - creates a camera. Currently only the perspective camera is supported (PerspectiveCamera)
- <a-obj-model>, <a-collada-model>, <a-gltf-model> - they all load and display models of the appropriate format.
- <a-cursor> is an element that allows you to perform various actions: click, pointing, etc. The cursor is tied to the center of the camera, so it will always be in the center of what the user sees.
- <a-image> - displays the selected image on the plane (<a-plane>).
- <a-link> is the same as a tag, only for a 3D scene.
- <a-sky> is a huge top hat around the scene that allows you to display 360 photos. Or you can just fill it with some color.
- <a-sound> - creates a sound source at a given position.
- <a-text> - draws flat text.
- <a-video> - plays the video on the plane.
I would also like to note that we work with DOM elements, and therefore we can use the standard DOM API, including querySelector, getElementById, appendChild, and so on.
A-Frame uses ECS
ECS (Entity Component System) - application and game design pattern. A widespread prevalence was just the same in the second segment. As the name implies, the three basic concepts of the pattern are Entity (Entity), Component (Component), System (System). In the classical form, they are interrelated with each other as follows: we have some container object (Entity) to which we can add components. Usually the component is responsible for a separate part of the logic. For example, we have a Player object (Player), it has a Health component. This component will contain all the logic associated with the replenishment or loss of health of the player (object). And systems, in turn, are needed in order to manage a set of entities united by some components. Typically, a component can register an entity within a system of the same name.
In A-Frame, this pattern is implemented very simply and elegantly - using attributes. As entities, any A-Frame elements are used -
<a-scene>, <a-box>, <a-sphere>, etc. But the
<a-entity> element stands apart of course. His name speaks for itself. All other elements are essentially wrappers for the components and are made for convenience, since any element can be created using the
<a-entity> . For example
<a-box> :
<a-entity geometry="primitive: box; width: 1; height: 1; depth: 1"></a-entity>
geometry is in this case the component that was added to the
<a-entity> entity . By itself, the
<a-entity> does not have any logic (in the global sense), and the
geometry component essentially turns it into a cube or something else. Another equally important component is the
material . It adds material to geometry. The material is responsible for whether our cube will shine like metal, will it have any textures, etc. In pure
Three.js we would have to create a separate geometry, a separate material, and then it would all have to be combined in the mesh. In general, this approach saves time.
Any component in A-Frame must be registered globally through a special design:
AFRAME.registerComponent('hello-world', { init: function () { console.log('Hello, World!'); } });
Then this component can be added to the scene, or any other element.
<a-entity hello-world></a-entity>
Since we added
init callback for our component, as soon as the element is added to the DOM, this callback will work and we will see our message in the console. There are other lifecycle callbacks in the A-Frame components. Let's look at them in more detail:
- update - called when initializing both init and updating any property of this component.
- remove - called after removing a component or an entity containing it. That is, if you remove the <a-entity> from the DOM, all its components will call remove the callback.
- tick - called every time before rendering the scene. Inside, the render loop uses requestAnimaitonFrame
- tock - called every time after rendering the scene.
- play - each is called when the scene rendering is resumed. Essentially after scene.play ();
- pause - called every time the scene rendering is stopped. Essentially after scene.pause ();
- updateSchema - called every time after updating the schema.
Another important component concept in A-Frame is the schema. The schema describes the properties of the component. It is defined as follows:
AFRAME.registerComponent('my-component', { schema: { arrayProperty: {type: 'array', default: []}, integerProperty: {type: 'int', default: 5} } }
In this case, our component
my-component will contain two properties
arrayProperty and
integerProperty . To pass them to the component, you must set the value of the corresponding attribute.
<a-entity my-component="arrayProperty: 1,2,3; integerProperty: 7"></a-entity>
You can get these properties inside the component through the
data property.
AFRAME.registerComponent('my-component', { schema: { arrayProperty: {type: 'array', default: []}, integerProperty: {type: 'int', default: 5} }, init: function () { console.log(this.data); } }
To get the properties of the component from the entity to which it is added, you can use the slightly modified
getAttribute function. When referring to an A-Frame entity, it returns not just the string value of the attribute, but the
data object mentioned above.
console.log(this.el.getAttribute('my-component'));
In approximately the same way, you can change the properties of a component:
this.el.setAttribute('my-component',{arrayProperty: [], integerProperty: 5})
Now let's talk about systems. Systems in an A-Frame are registered in a similar way as components:
AFRAME.registerSystem('my-system', { schema: {}, init: function () { console.log('Hello, System!'); }, });
As well as the component, the system has a scheme and callbacks. Only the system has only 5 of them:
init, play, pause, tick, tock . The system does not need to be added as a component to an entity. It will automatically be added to the scene.
this.el.sceneEl.systems['my-system'];
If the component has the same name as the system, then the system will be accessible by the link
this.system .
AFRAME.registerSystem('enemy', { schema: {}, init: function () {}, }); AFRAME.registerComponent('enemy', { schema: {}, init: function () { const enemySystem = this.system; }, });
Usually systems are needed in order to assemble and manage entities with the appropriate components. Here, in theory, should be the logic that belongs to the collection of entities. For example, the management of the appearance of enemies in the game.
Communication in A-Frame takes place via browser events.
And indeed, why reinvent the wheel, if at the moment there is an excellent embedded implementation of a publisher-subscriber for DOM elements. DOM events allow you to listen to browser events, such as pressing a keyboard key, clicking a mouse, and others, as well as custom events. A-Frame offers us a convenient and easy way to communicate between entities, components and systems, through user events. To do this, each A-Frame element is patched by the
emit function, it takes three parameters: the
first is the name of the event, the
second is the data that needs to be transmitted, the
third is whether the event should float.
this.el.emit('start-game', {level: 1}, false);
You can subscribe to this event in our usual way using
addEventListener: const someEntity = document.getElementById('someEntity'); someEntity.addEventListener('start-game', () => {...});
The bubbling of events here is a very important point, because sometimes two entities that must communicate with each other are on the same level, in which case you can add an event listener to the scene element. He as you could see earlier, is available inside each element through the
sceneEl link.
this.el.sceneEl.addEventListener('start-game', () => {...});
Finally
Here, perhaps, all that about what I wanted to tell in this article. In A-Frame there are still many different topics that could be sanctified, but this article is an overview and I wanted to focus the attention of the reader only on the main points. In the next article we will try to create a basic scene in order to test all the knowledge gained in practice. Thanks to all!