Разделение логики и отрисовки

В Paper.js основной принцип построения графических приложений заключается в чётком разграничении логики приложения и отрисовки графических объектов. Это позволяет создавать более читаемый, масштабируемый и поддерживаемый код, особенно в проектах с анимацией или интерактивными элементами.


Основные концепции

Логика приложения отвечает за:

  • Вычисления и обновление состояния объектов (координаты, скорость, направление, размер, цвет и др.)
  • Обработку событий (мышь, клавиатура, таймеры)
  • Правила взаимодействия объектов между собой

Отрисовка отвечает за:

  • Визуализацию текущего состояния объектов
  • Применение стилей (цвета, прозрачности, градиенты)
  • Создание и управление слоями (Layers)

В Paper.js объекты, такие как Path, Shape и Raster, являются визуальными представлениями состояния. Важно понимать, что логика не должна напрямую изменять визуальные свойства объектов, кроме как через обновление состояния, а отрисовка должна лишь интерпретировать это состояние.


Моделирование состояния отдельно от Paper.js объектов

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

class Ball {
    constructor(x, y, vx, vy, radius, color) {
        this.position = new Point(x, y);
        this.velocity = new Point(vx, vy);
        this.radius = radius;
        this.color = color;
    }

    update(delta) {
        this.position = this.position.add(this.velocity.multiply(delta));
    }
}

Здесь класс Ball полностью не зависит от Paper.js. Его методы только изменяют состояние объекта.


Связывание состояния с визуализацией

Для отрисовки создаются Paper.js объекты и синхронизируются со состоянием:

let balls = [
    new Ball(100, 100, 2, 3, 20, 'red'),
    new Ball(200, 150, -1, 2, 15, 'blue')
];

let ballShapes = balls.map(ball => {
    let shape = new Path.Circle(ball.position, ball.radius);
    shape.fillColor = ball.color;
    return shape;
});

Обновление позиции происходит в onFrame:

view.onFr ame = (event) => {
    balls.forEach((ball, i) => {
        ball.update(event.delta);
        ballShapes[i].position = ball.position;
    });
};

Таким образом, логика (ball.update) и отрисовка (ballShapes[i].position) полностью разделены, и Paper.js служит лишь визуальным интерфейсом.


Управление событиями

События можно обрабатывать аналогичным образом. Например, нажатие мыши изменяет состояние объектов, а Paper.js обновляет визуализацию:

tool.onMouseD own = (event) => {
    balls.forEach(ball => {
        let direction = ball.position.subtract(event.point).normalize();
        ball.velocity = direction.multiply(5);
    });
};

Здесь Paper.js не управляет физикой — она вычисляется логикой, а библиотека лишь визуально отражает результат.


Использование слоёв для отделения визуальных компонентов

Paper.js предоставляет объект Layer, позволяющий структурировать визуальные элементы:

let backgroundLayer = new Layer();
let objectsLayer = new Layer();

let background = new Path.Rectangle(view.bounds);
background.fillColor = 'lightgray';
backgroundLayer.addChild(background);

objectsLayer.activate();
balls.forEach(ball => {
    let shape = new Path.Circle(ball.position, ball.radius);
    shape.fillColor = ball.color;
});

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


Паттерны для больших проектов

  1. Модель-Вид-Контроллер (MVC)

    • Модель хранит состояние объектов.
    • Вид отвечает за Paper.js объекты и отрисовку.
    • Контроллер управляет событиями и изменяет модель.
  2. Система компонентов

    • Каждый объект игры или приложения состоит из компонентов состояния и визуальных компонентов, которые синхронизируются через менеджер сцены.
  3. Фреймбуферное обновление

    • В сложных анимациях удобно хранить состояние в отдельном буфере и в onFrame полностью перерисовывать объекты, что предотвращает расхождение логики и визуализации.

Практические рекомендации

  • Избегать вызова shape.translate, shape.scale или shape.rotate напрямую в логике.
  • Хранить все свойства объектов (позиция, скорость, угол, цвет) в отдельной модели.
  • Обновлять визуальные объекты только на основе состояния модели в view.onFrame.
  • Использовать слои для управления различными типами элементов и их рендерингом.
  • События мыши и клавиатуры должны изменять модель, а не Paper.js объекты напрямую.

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