О чем этот пример
Визуализация динамических волн и частиц — ключевой элемент для создания атмосферных эффектов в играх, будь то вода, магические поля или космические искажения. Этот пример демонстрирует, как используя всего один класс `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(), или привязать движение точек к курсору мыши.
