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

Визуальные эффекты, такие как шлейфы или эхо-следы за курсором, оживляют игровой мир. Их можно создавать не только через частицы, но и через прямое управление геометрией. В этом примере мы разберем, как с помощью метода `Phaser.Geom.Ellipse.CopyFrom()` и простого массива объектов создать динамический след из увеличивающихся эллипсов, следующих за указателем мыши. Этот подход демонстрирует основы работы с геометрическими объектами, их копированием и анимацией в реальном времени, что полезно для создания кастомных эффектов, траекторий или областей влияния.

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

Живой запуск

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

Исходный код


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

        const pointerEllipse = new Phaser.Geom.Ellipse(400, 300, 400, 300);

        const ellipses = [];

        for (let k = 0; k < 30; k++)
        {
            ellipses.push(new Phaser.Geom.Ellipse(0, 0, 0, 0));
        }

        let i = 0;

        this.input.on('pointermove', pointer =>
        {

            pointerEllipse.setTo(pointer.x, pointer.y, pointer.x / 8, pointer.y / 6);

            Phaser.Geom.Ellipse.CopyFrom(pointerEllipse, ellipses[i]);

            i = (i + 1) % ellipses.length;

            graphics.clear();

            graphics.strokeEllipseShape(pointerEllipse);

            for (let j = 0; j < ellipses.length; j++)
            {

                ellipses[j].width *= 1.1;
                ellipses[j].height *= 1.1;

                graphics.strokeEllipseShape(ellipses[j]);

            }

        });
    }
}

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

const game = new Phaser.Game(config);

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

В начале метода create() инициализируются все необходимые объекты. Ключевых элемента три: объект Graphics для рисования, главный эллипс, привязанный к курсору, и массив для хранения истории эллипсов.

const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x00aaaa } });

Здесь создается объект graphics с тонкой бирюзовой линией для отрисовки контуров.

const pointerEllipse = new Phaser.Geom.Ellipse(400, 300, 400, 300);
const ellipses = [];

for (let k = 0; k < 30; k++)
{
    ellipses.push(new Phaser.Geom.Ellipse(0, 0, 0, 0));
}

pointerEllipse — это основной эллипс, который будет повторять положение курсора. Изначально он создается в центре с большими размерами. Массив ellipses из 30 элементов — это "память" для следа. Каждый элемент массива предварительно создается как пустой эллипс в точке (0,0) с нулевыми размерами. Это необходимо для последующего заполнения их данными через копирование.

Обработка движения курсора и копирование геометрии

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

this.input.on('pointermove', pointer =>
{
    pointerEllipse.setTo(pointer.x, pointer.y, pointer.x / 8, pointer.y / 6);
    Phaser.Geom.Ellipse.CopyFrom(pointerEllipse, ellipses[i]);
    i = (i + 1) % ellipses.length;

Метод pointerEllipse.setTo() мгновенно перезаписывает координаты и размеры эллипса, основываясь на текущей позиции курсора (pointer.x, pointer.y). Ширина и высота зависят от координат, что создает интересную динамику формы.

Статический метод Phaser.Geom.Ellipse.CopyFrom(source, dest) является сердцем примера. Он копирует свойства эллипса-источника (pointerEllipse) в эллипс-назначение (ellipses[i]). Это эффективнее, чем создавать новый объект new Ellipse(...), так как переиспользует уже созданные экземпляры из массива.

Переменная `iвыступает в роли циклического индекса. Операция(i + 1) % ellipses.length` гарантирует, что после последнего элемента массива индекс снова станет равным 0, создавая кольцевой буфер.

Очистка, трансформация и отрисовка следа

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

graphics.clear();
graphics.strokeEllipseShape(pointerEllipse);

for (let j = 0; j < ellipses.length; j++)
{
    ellipses[j].width *= 1.1;
    ellipses[j].height *= 1.1;
    graphics.strokeEllipseShape(ellipses[j]);
}

Вызов graphics.clear() стирает все, что было нарисовано на предыдущем кадре. Это важно для анимации в реальном времени.

Затем отрисовывается контур главного эллипса с помощью graphics.strokeEllipseShape().

В цикле по массиву ellipses каждый эллипс трансформируется: его ширина и высота умножаются на 1.1. Это простое действие создает иллюзию постепенного роста и "растворения" эллипсов, которые были скопированы ранее. После увеличения эллипс также рисуется на холсте. Поскольку отрисовка происходит после увеличения размеров, самый старый эллипс в буфере оказывается самым большим.

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

Метод Phaser.Geom.Ellipse.CopyFrom() предоставляет производительный способ дублирования геометрических данных, идеально подходящий для создания эффектов на основе истории объектов, как в примере со следом. Для экспериментов попробуйте изменить правило увеличения размеров (например, добавлять фиксированное значение), менять цвет или прозрачность (graphics.lineStyle) у старых эллипсов или реализовать подобный след для других геометрических объектов, используя аналогичные методы копирования, например, Phaser.Geom.Rectangle.CopyFrom().