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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    start;
    hsv;
    graphics;
    size = 64 * 2;
    historyX = Array(this.size);
    historyY = Array(this.size);
    index = 0;

    create ()
    {
        this.graphics = this.add.graphics();

        //  diamond shape from 400x300 =

        //  400 x 100 (top middle)
        //  100 x 300 (left)
        //  700 x 300 (right)
        //  400 x 500 (bottom)

        this.start = new Phaser.Math.Vector2(400, 100);

        const top = new Phaser.Math.Vector2(400, 100);
        const left = new Phaser.Math.Vector2(100, 300);
        const right = new Phaser.Math.Vector2(700, 300);
        const bottom = new Phaser.Math.Vector2(400, 500);

        this.hsv = Phaser.Display.Color.HSVColorWheel();

        this.tweens.chain({
            targets: this.start,
            loop: -1,
            duration: 2000,

            tweens: [
                { x: left.x, y: left.y, ease: 'Sine.easeOut' },
                { x: bottom.x, y: bottom.y, ease: 'Sine.easeInOut' },
                { x: right.x, y: right.y, ease: 'Sine.easeInOut' },
                { x: top.x, y: top.y, ease: 'Sine.easeIn' }
            ]
        });


        //  Fill the history array
        for (let i = 0; i < this.size; i++)
        {
            this.historyX[i] = this.start.x;
            this.historyY[i] = this.start.y;
        }
    }

    update ()
    {
        this.historyX[this.index] = this.start.x;
        this.historyY[this.index] = this.start.y;

        this.index++;

        if (this.index === this.size)
        {
            this.index = 0;
        }

        this.graphics.clear();

        for (let i = 0; i < 64; i++)
        {
            this.graphics.fillStyle(this.hsv[i * 2].color, 1);
            this.graphics.fillCircle(this.historyX[i * 2], this.historyY[i * 2], 64);
        }
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и определение траектории

В методе create() инициализируются основные компоненты: объект Graphics для рисования и массив точек для хранения истории позиций. Траектория движения задается четырьмя точками, образующими ромб. Создается вектор start, который будет анимироваться.

this.graphics = this.add.graphics();
const top = new Phaser.Math.Vector2(400, 100);
const left = new Phaser.Math.Vector2(100, 300);
const right = new Phaser.Math.Vector2(700, 300);
const bottom = new Phaser.Math.Vector2(400, 500);

Также генерируется цветовое колесо HSV для получения радужной палитры и заполняется массив истории начальными координатами, чтобы избежать пустых значений при первом обновлении.

Настройка цепочки анимаций с помощью Tweens

Для плавного движения точки по ромбу используется this.tweens.chain(). Этот метод позволяет объединить несколько tween-анимаций в последовательную цепочку. Каждый tween перемещает вектор start к одной из вершин ромба с заданной функцией плавности (easing). Параметр loop: -1 обеспечивает бесконечное повторение цепочки.

this.tweens.chain({
    targets: this.start,
    loop: -1,
    duration: 2000,
    tweens: [
        { x: left.x, y: left.y, ease: 'Sine.easeOut' },
        { x: bottom.x, y: bottom.y, ease: 'Sine.easeInOut' },
        { x: right.x, y: right.y, ease: 'Sine.easeInOut' },
        { x: top.x, y: top.y, ease: 'Sine.easeIn' }
    ]
});

Использование разных функций ease (например, Sine.easeOut и Sine.easeIn) делает движение более естественным, имитируя ускорение и замедление.

Обновление истории позиций в реальном времени

В методе update() система постоянно обновляет историю координат. Текущие позиции вектора start записываются в массивы historyX и historyY по индексу index. Индекс увеличивается каждый кадр и сбрасывается при достижении размера массива, создавая кольцевой буфер. Это эффективно хранит последние 128 позиций.

this.historyX[this.index] = this.start.x;
this.historyY[this.index] = this.start.y;
this.index++;
if (this.index === this.size) {
    this.index = 0;
}

Такой подход позволяет отслеживать траекторию движения без бесконечного роста массивов.

Динамическая отрисовка радужного следа

После обновления истории происходит отрисовка. Сначала вызывается this.graphics.clear(), чтобы удалить графику предыдущего кадра. Затем в цикле рисуются 64 круга, каждый со своим цветом из HSV-палитры. Координаты берутся из истории с шагом 2, что создает разреженный, но равномерный след. Радиус кругов фиксирован (64 пикселя).

this.graphics.clear();
for (let i = 0; i < 64; i++) {
    this.graphics.fillStyle(this.hsv[i * 2].color, 1);
    this.graphics.fillCircle(this.historyX[i * 2], this.historyY[i * 2], 64);
}

Использование fillStyle с цветом из this.hsv и fillCircle для отрисовки создает яркий градиентный эффект вдоль траектории.

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

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