Обзорная статья по A-Frame



A-Frame — это веб-фреймворк позволяющий создавать различные приложения, игры, сцены в виртуальной реальности (ВР). Все вышеописанное будет доступно прямо из браузера вашего шлема ВР. Этот инструмент будет полезен как тем кто хочет заниматься разработкой ВР игр в браузере, так и например, может пригодится в качестве платформы для создания веб ВР приложений, сайтов, посадочных страниц. Сферы использования веб ВР ограничены лишь вашим воображением. Навскидку могу привести пару сфер деятельности человека где ВР может быть полезен: образование, медицина, спорт, продажи, отдых.

Что там внутри ?


A-Frame не написан с 0 на чистом WebGL, в его основе лежит библиотека Three.js. Поэтому рекомендую для начала разобраться с базовыми концепциями Three.js прежде чем начинать работать с A-Frame, хотя это и не обязательно, так как A-Frame устроен таким образом, чтобы вы меньше всего думали о рендеринге геометрии и больше концентрировались на логике вашего приложения. На то он, вообщем-то, и фреймворк.

Для этого A-Frame постулирует три основных положения, о которых мы и поговорим далее.

A-Frame работает с HTML


Многие базовые элементы A-Frame, такие как scene, camera, box, sphere и др., добавляются на сцену через одноименные теги с префиксом a-. Каждый подобный элемент зарегистрирован как пользовательский. На сегодняшний день A-Frame (0.8.0) использует спецификацию v0.

<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>

Данный небольшой фрагмент кода отрисует WebGL сцену к которой будут добавлены два объекта: куб и сфера с заданными параметрами. Помимо упомянутых выше двух элементов существует еще ряд других примитивов, которые могут быть добавлены на сцену таким же образом: <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>. Также в A-Frame существует ряд других элементов, которые выполняют определенные функции:

  • <a-camera> — создает камеру. На данный момент поддерживается только перспективная камера (PerspectiveCamera)
  • <a-obj-model>, <a-collada-model>, <a-gltf-model> — все они грузят и отображают модели соответствующего формата.
  • <a-cursor> — элемент позволяющий выполнять различные действия: клик, наведение и др. Курсор привязан к центру камеры, таким образом он всегда будет по центру того что видит пользователь.
  • <a-image> — отображает выбранное изображение на плоскости (<a-plane>).
  • <a-link> — то же самое что тег, только для 3D сцены.
  • <a-sky> — огромный цилиндр вокруг сцены, который позволяет отображать 360 фотографии. Или его просто можно залить каким-нибудь цветом.
  • <a-sound> — создает источник звука в заданной позиции.
  • <a-text> — рисует плоский текст.
  • <a-video> — проигрывает видео на плоскости.

Также хотелось бы отметить, что мы работаем с элементами DOM, а поэтому можем использовать стандартное DOM API, включая querySelector, getElementById, appendChild и тд.

A-Frame использует ECS



ECS (Entity Component System) — паттерн проектирования приложений и игр. Широкую распространенность получил как раз-таки во втором сегменте. Как видно из названия, три основные понятия паттерна это Entity (Сущность), Component (Компонент), System (Система). В классическом виде они взаимосвязаны друг с другом следующим образом: у нас есть некоторый объект-контейнер (Сущность), к которому можно добавлять компоненты. Обычно компонент отвечает за отдельную часть логики. Например, у нас есть объект Player (Игрок), у него есть компонент Health (Здоровье). Этот компонент будет содержать всю логику связанную с восполнением или потерей здоровья игрока (объекта). А системы, в свою очередь, нужны для того, чтобы управлять набором сущностей объединенных некоторыми компонентами. Обычно некий компонент может зарегистрировать сущность внутри одноименной системы.

В A-Frame этот паттерн реализован очень просто и элегантно — с помощью атрибутов. В качестве сущностей используются любые элементы A-Frame — <a-scene>, <a-box>, <a-sphere>, и др. Но особняком конечно же стоит элемент <a-entity>. Его имя говорит само за себя. Все остальные элементы являются по сути обертками для компонентов и сделаны для удобства, так как любой элемент можно создать и при помощи <a-entity>. Например <a-box>:

<a-entity geometry="primitive: box; width: 1; height: 1; depth: 1"></a-entity>

geometry — в данном случае является компонентом, который был добавлен к сущности <a-entity>. Сам по себе <a-entity> не имеет какой-либо логики (в глобальном смысле), а компонент geometry — по сути превращает его в куб или что-либо другое. Другим не менее важным чем geometry компонентом является material. Он добавляет к геометрии материал. Материал отвечает за то, будет ли наш куб блестеть как металл, будет ли иметь какие-либо текстуры и т.д. В чистом Three.js нам бы пришлось создавать отдельно геометрию, отдельно материал, а потом это все нужно было бы скомбинировать в мэше. В общем такой подход существенно экономит время.

Любой компонент в A-Frame должен быть зарегистрирован глобально через специальную конструкцию:

AFRAME.registerComponent('hello-world', {
  init: function () {
    console.log('Hello, World!');
  }
});

Затем этот компонент можно будет добавить на сцену, или любой другой элемент.

<a-entity hello-world></a-entity>

Так как мы добавили init коллбэк для нашего компонента, то как только элемент будет добавлен в DOM, данный коллбэк отработает и мы увидим наше сообщение в консоли. В компонентах A-Frame есть и другие коллбэки жизненного цикла. Давайте остановимся на них подробнее:

  • update — вызывается как при инициализации как init, так и при обновлении любого свойства данного компонента.
  • remove — вызывается после удаления компонента или сущности его содержащей. То есть, если вы удалите <a-entity> из DOM, все его компоненты вызовут remove коллбэк.
  • tick — вызывается каждый раз перед рендерингом сцены. Внутри цикл рендеринга использует requestAnimaitonFrame
  • tock — вызывается каждый раз после рендеринга сцены.
  • play — вызывается каждый при возобновлении рендеринга сцены. По сути после scene.play();
  • pause — вызывается каждый раз при остановке рендеринга сцены. По сути после scene.pause();
  • updateSchema — вызывается каждый раз после обновления схемы.

Еще одним важным концептом компонента в A-Frame является схема. Схема описывает свойства компонента. Она определяется следующим образом:

AFRAME.registerComponent('my-component', {
  schema: {
    arrayProperty: {type: 'array', default: []},
    integerProperty: {type: 'int', default: 5}
  }
}

В данном случае наш компонент my-component будет содержать два свойства arrayProperty и integerProperty. Чтобы передать их в компонент нужно задать значение соответствующего атрибута.

<a-entity my-component="arrayProperty: 1,2,3; integerProperty: 7"></a-entity>

Получить эти свойства внутри компонента можно через свойство data.

AFRAME.registerComponent('my-component', {
  schema: {
    arrayProperty: {type: 'array', default: []},
    integerProperty: {type: 'int', default: 5}
  },
  init: function () {
    console.log(this.data);
  }
}

Чтобы получить свойства компонента из сущности к которой он добавлен можно воспользоваться немного видоизмененной функцией getAttribute. При обращении к сущности A-Frame она вернет не просто строковое значение атрибута, а объект data, упомянутый выше.

 console.log(this.el.getAttribute('my-component')); 
// {arrayProperty: [1,2,3], integerProperty: 7}

Примерно таким же образом можно изменить свойства компонента:

this.el.setAttribute('my-component',{arrayProperty: [], integerProperty: 5}) 

Теперь поговорим о системах. Системы в A-Frame регистрируется похожим образом как и компоненты:

AFRAME.registerSystem('my-system', {
  schema: {},
  init: function () {
    console.log('Hello, System!');
  },
});

Так же как и компонент система имеет схему и коллбэки. Только у системы их всего 5: init, play, pause, tick, tock. Систему не нужно добавлять как компонент к сущности. Она автоматически будет добавлена к сцене.

this.el.sceneEl.systems['my-system']; 

Если компонент будет иметь такое же имя как и система, то система будет доступна по ссылке this.system.

AFRAME.registerSystem('enemy', {
  schema: {},
  init: function () {},
});

AFRAME.registerComponent('enemy', {
  schema: {},
  init: function () {
    const enemySystem = this.system;
  },
});

Обычно системы нужны для того чтобы собирать и управлять сущностями с соответствующими компонентами. Тут, по идее, должна находится логика, которая относится к коллекции сущностей. Например, управление появлением врагов в игре.

Коммуникация в A-Frame происходит по средствам событий браузера


И действительно, зачем изобретать колесо, если на данный момент существует прекрасная встроенная реализация издатель-подписчик для элементов DOM. События DOM позволяют слушать как события браузера, такие как нажатие на клавишу клавиатуры, клик мышкой и другие, так и пользовательские события. A-Frame предлагает нам удобный и простой способ коммуникации между сущностями, компонентами и системами, через пользовательские события. Для этого каждый элемент А-Frame пропатчен функцией emit, она принимает три параметра: первый — название события, второй — данные которое нужно передать, третий — должно ли событие всплывать.

this.el.emit('start-game', {level: 1}, false);

Подписаться на данное событие можно привычным всем нам способом, используя addEventListener:

const someEntity = document.getElementById(‘someEntity’);
someEntity.addEventListener(‘start-game’, () => {...});

Всплывание событий (bubbling) тут является очень важным моментом, ведь иногда две сущности которые должны коммуницировать с друг другом находятся на одном уровне, в таком случае можно добавить слушатель события на элемент сцены. Он как вы могли видеть ранее, доступен внутри каждого элемента через ссылку sceneEl.

this.el.sceneEl.addEventListener(‘start-game’, () => {...});

В заключение


Вот, пожалуй, и все что о чем я хотел рассказать в данной статье. В A-Frame есть еще много различных тем которые можно было бы освятить, но это статья является обзорной и я хотел сконцентрировать внимание читателя только на основных моментах. В следующей статье мы попробуем создать базовую сцену, чтобы опробовать все полученые знания на практике. Всем спасибо!
  • +17
  • 2,5k
  • 1
Поделиться публикацией

Комментарии 1

    +1
    Спасибо, было интересно ознакомиться!

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое