О чем этот пример
Визуальные эффекты — это сердце атмосферы игры. Этот пример демонстрирует, как с помощью всего одного графического объекта `Graphics` и математики можно создать сложную, динамическую, почти трехмерную анимацию вращающихся частиц. Вы научитесь не просто рисовать примитивы, а управлять их поведением во времени, создавая иллюзию глубины и объема, что идеально подходит для фонов, загрузочных экранов или магических эффектов. Мы разберем по косточкам, как работает этот гипнотический спин.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
palette;
graphics;
inc = 0;
time = 0;
create ()
{
this.graphics = this.add.graphics({ x: 400, y: 300 });
this.palette = [ 0, 1911635, 8267091, 34641, 11227702, 6248271, 12764103, 16773608, 16711757, 16753408, 16772135, 58422, 2731519, 8615580, 16742312, 16764074 ];
}
update ()
{
this.time += 0.03;
this.time = Phaser.Math.Wrap(this.time, -32765, 32765);
this.graphics.clear();
const f = this.time / 9;
const n = 650 + 60 * this.sin(f / 3);
for (let i = 1; i < n; i++)
{
let a = f + Math.random();
let d = 0.3 + Math.random() * 2;
let y = -2;
if (i > 400)
{
const j = i - 400;
y = j * 2 / n - 1;
a = j * 40 / n + f + j / 3;
d = j * 3 / n;
}
let x = d * this.cos(a);
const z = 2 + this.cos(f) + d * this.sin(a);
x = 64 + x * 64 / z;
y = 64 + y * 64 / z;
const c = 6 + i % 5;
const e = 5 / z;
if (z > 0.1)
{
this.graphics.fillStyle(this.palette[c]);
if (i > 400)
{
this.graphics.fillCircle(x, y, e);
}
else
{
this.graphics.fillRect(x, y, e, e);
}
// graphics.fillStyle(palette[c / 4]);
// graphics.fillCircle(x, 128 - y, e);
}
}
}
cos (f)
{
return Math.cos(f * (Math.PI * 2));
}
sin (f)
{
return Math.sin(f * (Math.PI * 2));
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и палитры
В методе create() инициализируются ключевые объекты. Graphics — это холст для рисования векторных фигур. Мы позиционируем его в центре экрана.
Палитра palette — это массив чисел, представляющих цвета в формате RGB (например, 16711757 — это красный). Эти цвета будут использоваться для отрисовки частиц.
this.graphics = this.add.graphics({ x: 400, y: 300 });
this.palette = [ 0, 1911635, 8267091, 34641, 11227702, 6248271, 12764103, 16773608, 16711757, 16753408, 16772135, 58422, 2731519, 8615580, 16742312, 16764074 ];
Управление временем и очистка кадра
В update() управляется анимация. Переменная time увеличивается каждому кадру, создавая основу для движения. Чтобы избежать переполнения чисел (очень больших или очень малых значений), используется Phaser.Math.Wrap, который заворачивает значение time в заданный диапазон.
Перед каждой отрисовкой нового кадра старые фигуры нужно стереть с помощью clear().
this.time += 0.03;
this.time = Phaser.Math.Wrap(this.time, -32765, 32765);
this.graphics.clear();
Магия псевдо-3D: расчет позиции и глубины
Здесь создается иллюзия трехмерности. Параметр `n` определяет общее количество частиц, и оно слегка пульсирует благодаря синусу.
В цикле для каждой частицы рассчитываются углы (`a), расстояние (d`) и координата Y. Для частиц с индексом больше 400 (внешнее кольцо) расчеты сложнее — они формируют спиральную структуру.
Ключевой трюк — расчет координат `xиy. Деление наz` (расчетную "глубину") имитирует перспективу: чем дальше объект, тем он меньше и ближе к центру экрана.
const f = this.time / 9;
const n = 650 + 60 * this.sin(f / 3);
let a = f + Math.random();
let d = 0.3 + Math.random() * 2;
let y = -2;
// ... логика для i > 400 ...
let x = d * this.cos(a);
const z = 2 + this.cos(f) + d * this.sin(a);
x = 64 + x * 64 / z;
y = 64 + y * 64 / z;
Отрисовка фигур с учетом глубины
После расчета позиции и глубины частицы можно рисовать. Размер частицы (`e) обратно пропорционален глубинеz` — еще один штрих к 3D-иллюзии.
Цвет выбирается из палитры по индексу. Внутренние частицы (индекс <= 400) рисуются как квадраты (fillRect), а внешние — как круги (fillCircle), что добавляет визуального разнообразия.
Условие if (z > 0.1) отсекает отрисовку частиц, которые теоретически находятся "позади" камеры.
const c = 6 + i % 5;
const e = 5 / z;
if (z > 0.1)
{
this.graphics.fillStyle(this.palette[c]);
if (i > 400)
{
this.graphics.fillCircle(x, y, e);
}
else
{
this.graphics.fillRect(x, y, e, e);
}
}
Вспомогательные функции для цикличных волн
Стандартные Math.sin и Math.cos работают с радианами. Наши функции sin и cos принимают аргумент `f, который является долей полного круга (где 1 = 360 градусов). Это делает код вupdate()` более читаемым, так как мы оперируем не радианами, а «оборотами».
cos (f)
{
return Math.cos(f * (Math.PI * 2));
}
sin (f)
{
return Math.sin(f * (Math.PI * 2));
}
Что попробовать дальше
Этот пример — отличная отправная точка для создания собственных procedural visual effects. Экспериментируйте: измените палитру на более холодную или огненную, замените квадраты на треугольники (используя fillPath), управляйте количеством частиц в реальном времени в ответ на действия игрока или добавьте вторую симметричную спираль, раскомментировав закомментированные строки в коде. Главный вывод: мощная визуализация в Phaser часто строится не на сложных ассетах, а на грамотном применении математики к простым графическим примитивам.
