Модификация сцены вне цикла рендера

В A-Frame сцена представляет собой дерево сущностей (entities), корневым элементом которого является <a-scene>. Каждая сущность (<a-entity>) может содержать компоненты, задающие геометрию, материалы, поведение, события и взаимодействия. Динамическая модификация сцены вне цикла рендера позволяет создавать, удалять и изменять объекты на лету без необходимости перезагружать сцену или вручную управлять рендерингом WebGL.

Для работы с сущностями используется стандартный DOM-API. Любой элемент можно добавить через document.createElement('a-entity') и установить для него необходимые компоненты с помощью метода setAttribute. Например:

const entity = document.createElement('a-entity');
entity.setAttribute('geometry', { primitive: 'box', height: 2, width: 2, depth: 2 });
entity.setAttribute('material', { color: '#FF0000' });
document.querySelector('a-scene').appendChild(entity);

В этом примере создается красный куб, который автоматически появляется в сцене без участия цикла рендера.


Управление атрибутами компонентов

Каждый компонент в A-Frame можно изменять динамически через setAttribute или removeAttribute. Это позволяет обновлять свойства объектов в любой момент времени. Например, изменение цвета материала:

const box = document.querySelector('#myBox');
box.setAttribute('material', 'color', '#00FF00');

Важно понимать различие между передачей объекта и строки при вызове setAttribute:

  • box.setAttribute('material', { color: '#00FF00' }); — передача объекта JavaScript.
  • box.setAttribute('material', 'color: #00FF00'); — передача строки, аналог CSS.

Использование объектов предпочтительно при необходимости обновления нескольких свойств одновременно, а строки удобны для единичных изменений.


Добавление и удаление сущностей

Для создания и удаления объектов вне цикла рендера применяются стандартные методы DOM:

  • appendChild(entity) — добавление объекта в сцену.
  • removeChild(entity) — удаление объекта.
  • replaceChild(newEntity, oldEntity) — замена сущности на другую.

Пример динамического удаления:

const entity = document.querySelector('#oldBox');
if (entity) {
  entity.parentNode.removeChild(entity);
}

Такая модификация не требует перезапуска сцены и отражается мгновенно.


Взаимодействие с событиями

A-Frame предоставляет богатый набор событий (click, mouseenter, mouseleave, loaded, componentchanged), которые можно использовать для модификации сцены. События позволяют менять свойства объектов или добавлять новые сущности в ответ на действия пользователя или внешние триггеры:

const button = document.querySelector('#spawnButton');
button.addEventListener('click', () => {
  const sphere = document.createElement('a-entity');
  sphere.setAttribute('geometry', { primitive: 'sphere', radius: 1 });
  sphere.setAttribute('material', { color: '#0000FF' });
  document.querySelector('a-scene').appendChild(sphere);
});

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


Использование компонентов для динамических изменений

Компоненты A-Frame позволяют инкапсулировать логику модификации сцены. Создание собственного компонента обеспечивает переиспользуемость и чистоту кода. Пример компонента, который периодически изменяет цвет объекта:

AFRAME.registerComponent('color-changer', {
  schema: { interval: { type: 'int', default: 1000 } },
  init: function () {
    this.changeColor = this.changeColor.bind(this);
    this.intervalId = setInterval(this.changeColor, this.data.interval);
  },
  changeColor: function () {
    const colors = ['#FF0000', '#00FF00', '#0000FF'];
    const color = colors[Math.floor(Math.random() * colors.length)];
    this.el.setAttribute('material', 'color', color);
  },
  remove: function () {
    clearInterval(this.intervalId);
  }
});

Применение к сущности:

<a-entity geometry="primitive: box" material="color: #FFF" color-changer="interval: 500"></a-entity>

Этот компонент обновляет цвет объекта каждые 500 мс, полностью управляя состоянием вне основного цикла рендера.


Анимации вне рендера

Хотя A-Frame предоставляет <a-animation> и систему animation-mixer, простые динамические изменения можно выполнять напрямую через JavaScript. Пример плавного перемещения объекта:

const box = document.querySelector('#myBox');
let position = { x: 0, y: 1, z: -5 };

function moveBox() {
  position.x += 0.01;
  box.setAttribute('position', position);
  requestAnimationFrame(moveBox);
}

moveBox();

Использование requestAnimationFrame обеспечивает синхронизацию с рендером браузера, но логика обновления все равно находится вне стандартного рендер-цикла A-Frame.


Оптимизация изменений сцены

При динамическом создании большого числа сущностей важно учитывать производительность:

  • Избегать частого создания и удаления объектов, лучше использовать visible для временного скрытия.
  • Группировать объекты с помощью <a-entity> контейнеров для уменьшения числа DOM-операций.
  • Минимизировать обновление компонентов каждый кадр, использовать события и таймеры.
  • Для сложной логики анимации применять кастомные компоненты с управлением интервалами.

Взаимодействие с системами

A-Frame использует систему systems для управления общими ресурсами сцены. Динамическая модификация может взаимодействовать с системами, например, управляя физикой через aframe-physics-system:

const entity = document.createElement('a-entity');
entity.setAttribute('geometry', { primitive: 'sphere', radius: 0.5 });
entity.setAttribute('material', { color: '#FFFF00' });
entity.setAttribute('dynamic-body', {}); // компонент физики
document.querySelector('a-scene').appendChild(entity);

Это позволяет объекту сразу участвовать в симуляции физики после добавления, без повторной инициализации сцены.


Доступ к сущностям по селекторам

A-Frame позволяет использовать как querySelector, так и querySelectorAll для поиска объектов и массовой модификации:

document.querySelectorAll('.interactive').forEach(el => {
  el.setAttribute('material', 'opacity', 0.5);
});

Присваивание классов и идентификаторов делает динамическое управление более структурированным, особенно в больших сценах с сотнями объектов.


Работа с шаблонами сущностей

Для упрощения создания объектов можно хранить заранее определенные шаблоны в HTML или JSON и клонировать их:

const template = document.querySelector('#enemyTemplate');
const clone = template.content.firstElementChild.cloneNode(true);
document.querySelector('a-scene').appendChild(clone);

Использование шаблонов ускоряет добавление сложных объектов и поддерживает единообразие компонентов.