О чем этот пример

При работе с геометрией и физикой в играх часто требуется создавать сложные траектории или формы, изменяя позиции точек. Прямое изменение исходного вектора может привести к трудноуловимым ошибкам, когда одна и та же точка используется в нескольких местах логики. В этой статье разберем, как правильно использовать метод `clone()` класса `Phaser.Math.Vector2` для безопасного создания спиральной траектории. Этот подход полезен для генерации путей движения снарядов, траекторий камеры или декоративных элементов.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x2266aa } });

        let point = new Phaser.Math.Vector2(400, 275);
        const points = [];

        let angle = 0;
        let length = 1;

        while (point.y < 600)
        {
            length += 0.05 / length;
            angle += 0.05;

            point = point.clone(); // Clone the point to avoid modifying the original

            point.x += Math.cos(angle) * length;
            point.y += Math.sin(angle) * length;

            points.push(point);
        }

        graphics.strokePoints(points);
    }
}

const config = {
    width: 800,
    height: 600,
    type: Phaser.AUTO,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Проблема: мутация исходных данных

В JavaScript объекты, включая векторы Phaser, передаются по ссылке. Это означает, что изменение свойств вектора (например, point.x) в одном месте кода повлияет на все остальные ссылки на этот же объект.

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

Решение: метод Vector2.clone()

Класс Phaser.Math.Vector2 предоставляет метод clone(), который создаёт и возвращает новый экземпляр вектора с идентичными значениями свойств `xиy`. Это самый простой и эффективный способ получить копию без связи с оригиналом.

В коде примера ключевая строка выглядит так:

point = point.clone(); // Clone the point to avoid modifying the original

На каждой итерации цикла мы создаём копию текущей точки, а затем уже модифицируем эту копию, вычисляя следующую позицию на спирали. Оригинальный вектор, добавленный в массив points на предыдущем шаге, остаётся неизменным.

Построение спиральной траектории

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

Сначала создаются начальные объекты:

const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x2266aa } });
let point = new Phaser.Math.Vector2(400, 275);
const points = [];

Объект graphics будет рисовать линии. Вектор point — это начальная точка спирали. Массив points будет хранить все точки траектории для последующей отрисовки.

Далее идет цикл, который выполняется, пока Y-координата точки меньше 600:

while (point.y < 600)
{
    length += 0.05 / length;
    angle += 0.05;

    point = point.clone();

    point.x += Math.cos(angle) * length;
    point.y += Math.sin(angle) * length;

    points.push(point);
}

Переменные length (радиус) и angle плавно увеличиваются на каждом шаге. После клонирования новой позиции рассчитываются через тригонометрические функции Math.cos и Math.sin. Полученная точка добавляется в массив.

Итоговая отрисовка:

graphics.strokePoints(points);

Метод strokePoints() объекта Graphics соединяет все точки из массива линиями, визуализируя спираль.

Альтернативы и лучшие практики

Хотя clone() идеален для ясности, иногда можно обойтись созданием нового вектора «вручную». Однако это менее читаемо:

// Менее предпочтительная альтернатива
point = new Phaser.Math.Vector2(point.x, point.y);

Главное правило: если вам нужно сохранить предыдущее состояние вектора для других вычислений или отладки, всегда создавайте его копию перед модификацией. Это особенно критично в рамках игрового цикла update(), где объекты часто переиспользуются.

Также помните, что методы, изменяющие сам объект (например, add(), scale()), обычно возвращают this. Если вы хотите получить результат операции, не меняя исходник, используйте их статические аналоги или предварительное клонирование:

let result = point.clone().add(velocity);

Что попробовать дальше

Использование clone() — это простой, но мощный паттерн для безопасной работы с векторной математикой в Phaser. Он гарантирует целостность данных и предотвращает случайные мутации. Для экспериментов попробуйте изменить приращение угла и длины в примере, чтобы получить спирали разной формы. Или используйте этот подход для создания сложного пути (Phaser.Curves.Path) из массива клонированных точек, по которому затем сможет двигаться спрайт.