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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    points;
    step = 0.5;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 1, color: 0x2266aa } });

        this.points = [];
    }

    update ()
    {
        if (this.step <= Math.PI / 50)
        {
            return;
        }
        else
        {
            this.step = Math.max(this.step * 0.998, Math.PI / 50);
        }

        this.graphics.clear();

        let i = 0;

        for (let angle = 0; angle < Math.PI * 2; angle += this.step)
        {
            const point = this.points[i] || new Phaser.Math.Vector2();

            point.setTo(Math.cos(angle) * 290, Math.sin(angle) * 290);

            this.points[i] = point;

            ++i;
            this.points[i] = point.clone().negate();

            ++i;
        }

        for (let j = 0; j < this.points.length; j++)
        {
            this.points[j].x += 400;
            this.points[j].y += 300;
        }

        this.graphics.strokePoints(this.points, false);
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и данных

В начале создается базовый каркас сцены. Инициализируются массивы для хранения точек и объект Graphics для их отрисовки.

class Example extends Phaser.Scene
{
    points;
    step = 0.5;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 1, color: 0x2266aa } });
        this.points = [];
    }

Ключевые объекты: - points: пустой массив для хранения объектов Vector2. - step: начальный угловой шаг для расчета позиций точек на окружности. - graphics: контекст отрисовки с заданным стилем линии.

Динамическое обновление и расчет точек

В методе update() происходит основная логика. Сначала уменьшается угловой шаг step, что делает анимацию плавно замедляющейся.

if (this.step <= Math.PI / 50)
{
    return;
}
else
{
    this.step = Math.max(this.step * 0.998, Math.PI / 50);
}

Затем холст очищается, и начинается цикл по углам от 0 до 2π. Для каждого угла вычисляется точка на окружности радиусом 290 пикселей.

for (let angle = 0; angle < Math.PI * 2; angle += this.step)
{
    const point = this.points[i] || new Phaser.Math.Vector2();
    point.setTo(Math.cos(angle) * 290, Math.sin(angle) * 290);
    this.points[i] = point;

Здесь point.setTo(x, y) задает координаты вектора. Важный момент: если в массиве points уже есть вектор по индексу `i`, он перезаписывается, что экономит память.

Создание симметрии с помощью negate()

Следующая строка — сердце примера. После сохранения исходной точки, индекс увеличивается, и в массив добавляется её зеркальная копия.

++i;
    this.points[i] = point.clone().negate();

Метод clone() создает новый объект Vector2 с теми же координатами, что и point. Затем метод negate() инвертирует знак обеих компонент этого нового вектора (x = -x, y = -y). Таким образом, для каждой точки на окружности мы мгновенно получаем её противоположность относительно центра (0, 0), создавая симметричную фигуру.

Смещение и отрисовка фигуры

После генерации всех пар точек, их необходимо сместить в центр холста игры, так как изначально они рассчитываются относительно координат (0, 0).

for (let j = 0; j < this.points.length; j++)
{
    this.points[j].x += 400;
    this.points[j].y += 300;
}

Значения 400 и 300 — это смещение, примерно соответствующее центру холста размером 800x600. Наконец, все точки соединяются линией.

this.graphics.strokePoints(this.points, false);

Метод strokePoints() объекта Graphics принимает массив точек и флаг, указывающий, нужно ли замыкать фигуру (в нашем случае — нет).

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

Метод negate() — это мощный и лаконичный способ работы с векторной симметрией. Он избавляет от необходимости вручную вычислять противоположные координаты. Для экспериментов попробуйте изменить радиус окружности, поиграть со скоростью уменьшения шага step или применить negate() не ко всем, а к каждому второму вектору для создания более сложных асимметричных паттернов. Также можно поэкспериментировать с цветом или толщиной линии в зависимости от индекса точки.