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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        this.graphics = this.add.graphics({ fillStyle: { color: 0x2266aa } });

        this.points = [];

        this.angle = 0;

        for (let i = 0; i <= 800 / 5; i++)
        {
            this.points.push(new Phaser.Math.Vector2(i * 5));
        }
    }

    update ()
    {
        this.graphics.clear();

        this.angle += 0.05;

        for (let i = 0; i < this.points.length; i++)
        {
            if (this.points[i].x > 0)
            {
                this.points[i].x -= 5;
                this.points[i].y -= Math.sin(this.angle + this.points[i].x / 400 * Math.PI) * 3;
            }
            else
            {
                this.points[i].setTo(800, Math.sin(this.angle) * 150 + 300);
            }

            this.graphics.fillPointShape(this.points[i], 7);
        }
    }
}

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

const game = new Phaser.Game(config);

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

В методе create() происходит инициализация графики и создание массива точек. Каждая точка — это экземпляр Phaser.Math.Vector2. Изначально они выстраиваются в линию вдоль оси X.

create ()
{
    this.graphics = this.add.graphics({ fillStyle: { color: 0x2266aa } });

    this.points = [];
    this.angle = 0;

    for (let i = 0; i <= 800 / 5; i++)
    {
        this.points.push(new Phaser.Math.Vector2(i * 5));
    }
}
Ключевые моменты:
1.  `this.add.graphics()` создаёт холст для рисования примитивов. Цвет заливки `0x2266aa` задаёт синий оттенок.
2.  `this.points` — массив, который будет хранить все наши векторные точки.
3.  Цикл заполняет массив векторами. Параметр `i * 5` передаётся в конструктор `Vector2(x, y)`. Если передан только один аргумент, он присваивается и X, и Y. Однако в следующем шаге мы будем перезаписывать Y, так что начальное значение не так важно.

Алгоритм движения в update()

Метод update() вызывается каждый кадр. Здесь происходит очистка холста, обновление угла и перерасчёт позиций всех точек.

update ()
{
    this.graphics.clear();
    this.angle += 0.05;

    for (let i = 0; i < this.points.length; i++)
    {
        if (this.points[i].x > 0)
        {
            this.points[i].x -= 5;
            this.points[i].y -= Math.sin(this.angle + this.points[i].x / 400 * Math.PI) * 3;
        }
        else
        {
            this.points[i].setTo(800, Math.sin(this.angle) * 150 + 300);
        }
        this.graphics.fillPointShape(this.points[i], 7);
    }
}
Разберём логику внутри цикла:
1.  **Условие `if (this.points[i].x > 0)`**: Пока точка находится в правой части экрана (X > 0), она движется влево и колеблется.
    *   `this.points[i].x -= 5;` — сдвигает точку на 5 пикселей влево каждый кадр.
    *   `this.points[i].y -= Math.sin(...) * 3;` — вычисляет вертикальное смещение. Синус принимает аргумент, который зависит от глобального `this.angle` и текущей координаты X точки. Деление на 400 и умножение на `Math.PI` масштабируют волну. Результат синуса (от -1 до 1) умножается на 3 — это амплитуда колебаний.
2.  **Блок `else`**: Когда точка достигает левого края (X <= 0), она «перебрасывается» в правую часть.
    *   `this.points[i].setTo(800, ...)` — метод `setTo` моментально задаёт новые координаты вектору. X устанавливается в 800 (правый край).
    *   Y вычисляется как `Math.sin(this.angle) * 150 + 300`. Это создаёт волнообразное движение для всей «ленты» точек в месте её появления.

Визуализация точек

После вычисления новой позиции точки её необходимо отрисовать.

this.graphics.fillPointShape(this.points[i], 7);

Метод fillPointShape объекта Graphics принимает два аргумента: 1. Объект с координатами. Наш Phaser.Math.Vector2 идеально подходит, так как имеет свойства `xиy`. 2. Радиус точки в пикселях. В нашем случае это 7, что создаёт довольно крупные и хорошо видимые круги. Важно, что отрисовка происходит каждый кадр после clear(), что создаёт иллюзию плавного движения.

Настройка игры (Config)

Конфигурационный объект задаёт основные параметры игры и связывает её со сценой.

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

const game = new Phaser.Game(config);

* width / height: Размер игрового холста. * type: Рендерер. Phaser.AUTO позволяет Phaser самому выбрать между WebGL и Canvas. * parent: ID HTML-элемента, в который будет встроен canvas. Если элемента нет, он будет создан в body. * scene: Класс, который будет использован в качестве основной сцены. У нас это наш Example. Инициализация игры происходит через создание экземпляра new Phaser.Game(config).

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

Этот пример — отличная основа для создания сложных частичных систем. Используя всего один массив векторов и синусоидальные функции, мы получили плавную, бесконечную анимацию волны. Для экспериментов попробуйте: изменить формулу внутри Math.sin() для создания более сложных паттернов (например, добавить косинус), варьировать скорость (this.angle += ...) и амплитуду колебаний, рандомизировать размер точек или их цвет, используя this.graphics.fillStyle(), или привязать движение точек к курсору мыши.