Компонентный подход

Компонентный подход в контексте Two.js подразумевает разбиение графической сцены на независимые, переиспользуемые элементы — компоненты, каждый из которых управляет своей геометрией, свойствами и поведением. Такой подход повышает масштабируемость кода и облегчает поддержку сложных визуальных проектов.


Создание компонентов

В Two.js каждый компонент обычно представляется как класс или функция-конструктор, содержащая:

  • геометрию (Two.Rectangle, Two.Circle, Two.Path и т.д.);
  • стиль (fill, stroke, linewidth);
  • методы обновления для анимации или изменения состояния.

Пример создания простого компонента:

class Ball {
  constructor(two, x, y, radius, color) {
    this.shape = two.makeCircle(x, y, radius);
    this.shape.fill = color;
    this.shape.noStroke();
  }

  move(dx, dy) {
    this.shape.translation.x += dx;
    this.shape.translation.y += dy;
  }

  setColor(color) {
    this.shape.fill = color;
  }
}

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


Организация сцены через контейнеры

Two.js поддерживает группы объектов через Two.Group, что позволяет объединять несколько компонентов в один логический блок. Группы упрощают управление положением, масштабом и видимостью нескольких элементов одновременно.

const group = two.makeGroup();
const ball1 = new Ball(two, 50, 50, 20, '#FF0000');
const ball2 = new Ball(two, 100, 50, 20, '#00FF00');

group.add(ball1.shape, ball2.shape);
group.translation.set(200, 100);

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


Параметризация и переиспользуемость

Компоненты должны быть параметризированы для максимальной переиспользуемости. Это включает:

  • координаты и размеры (x, y, radius, width, height);
  • цвета и стили (fill, stroke, opacity);
  • поведение (speed, direction, rotation).

Пример универсального компонента прямоугольника:

class RectangleComponent {
  constructor(two, options) {
    const { x, y, width, height, fill, stroke } = options;
    this.shape = two.makeRectangle(x, y, width, height);
    this.shape.fill = fill || 'black';
    this.shape.stroke = stroke || 'none';
  }

  resize(width, height) {
    this.shape.width = width;
    this.shape.height = height;
  }

  move(dx, dy) {
    this.shape.translation.x += dx;
    this.shape.translation.y += dy;
  }
}

Использование объектов options позволяет создавать множество вариаций одного и того же компонента без дублирования кода.


Интеграция анимации

Two.js предоставляет цикл обновления через two.update() и события two.bind('update', ...). Компоненты могут самостоятельно управлять своей анимацией, используя внутренние методы.

two.bind('update', () => {
  ball1.move(1, 0);   // движение по оси X
  ball2.move(-1, 0);  // движение в противоположном направлении
});

Компоненты могут содержать внутренние состояния для анимации, такие как скорость, направление или текущее положение, что делает их автономными.


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

Two.js поддерживает привязку событий к DOM-элементу канваса, что позволяет компонентам реагировать на пользовательские действия. Компоненты могут самостоятельно обрабатывать события, фильтруя их по своей области.

two.renderer.domElement.addEventListener('click', (event) => {
  const { offsetX, offsetY } = event;
  if (Math.hypot(offsetX - ball1.shape.translation.x, offsetY - ball1.shape.translation.y) < 20) {
    ball1.setColor('#0000FF');
  }
});

Такая организация позволяет изолировать логику компонентов от общей сцены.


Наследование и расширяемость

Компонентный подход упрощает создание сложных объектов через наследование. Общие свойства и методы базового компонента выносятся в родительский класс:

class ShapeComponent {
  constructor(shape) {
    this.shape = shape;
  }

  move(dx, dy) {
    this.shape.translation.x += dx;
    this.shape.translation.y += dy;
  }
}

class CircleComponent extends ShapeComponent {
  constructor(two, x, y, radius, color) {
    super(two.makeCircle(x, y, radius));
    this.shape.fill = color;
  }
}

Наследование позволяет создавать разнообразные элементы с минимальным дублированием кода.


Рекомендации по структуре проекта

  1. Каждый компонент в отдельном файле — улучшает читаемость и поддержку.
  2. Группы компонентов для логических блоков сцены (например, интерфейсные элементы или персонажи).
  3. Централизованное управление анимацией через общий цикл обновления, при этом компоненты остаются автономными.
  4. Параметризация через объекты настроек — упрощает создание вариаций без изменения исходного кода класса.

Примеры сложных компонентов

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

class RoboticArm {
  constructor(two, baseX, baseY) {
    this.base = new RectangleComponent(two, { x: baseX, y: baseY, width: 20, height: 60, fill: '#666' });
    this.joint = new CircleComponent(two, baseX, baseY - 30, 10, '#999');
    this.hand = new RectangleComponent(two, { x: baseX, y: baseY - 60, width: 10, height: 40, fill: '#333' });
  }

  rotate(angle) {
    this.hand.shape.rotation = angle;
    this.joint.shape.rotation = angle;
  }
}

Компоненты base, joint и hand работают как единый объект, но остаются индивидуально управляемыми.


Преимущества компонентного подхода

  • Модульность — легкая замена и доработка отдельных элементов.
  • Переиспользуемость — компоненты можно использовать в разных сценах и проектах.
  • Легкость тестирования — каждый компонент можно тестировать отдельно.
  • Простота масштабирования — сцена растет без усложнения архитектуры.

Этот подход обеспечивает гибкое управление визуальными объектами в Two.js и является основой для разработки интерактивных и анимированных приложений с чистой архитектурой.