О чем этот пример
Визуальные эффекты, такие как плавное движение множества объектов по заданной траектории, часто используются в играх для создания гипнотических заставок, следов за заклинаниями или формирования "живых" интерфейсов. В этом примере мы рассмотрим, как с помощью встроенного в Phaser 3 модуля `Follower` и кривых Безье (`Curves`) легко создать анимированную цепочку из двадцати частиц, которая движется по сложному замкнутому пути. Это полезно не только для декоративных элементов, но и для проектирования путей патрулирования врагов или траекторий снарядов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ball', 'assets/particles/sparkle1.png');
}
create ()
{
const points = [
50, 300, 179, 449, 394, 498, 593, 455,
701, 338, 692, 190, 603, 76, 423, 41,
272, 78, 181, 186, 230, 328, 416, 395,
565, 327, 550, 202, 467, 149, 355, 164,
343, 254, 428, 303
];
const curve = new Phaser.Curves.Spline(points);
for (let i = 0; i < 20; i++)
{
const follower = this.add.follower(curve, 100, 100 + (30 * i), 'ball');
follower.setScale(0.5);
follower.setBlendMode(Phaser.BlendModes.ADD);
follower.startFollow({
duration: 4500,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
_delay: i * 200,
delay: i * 100
});
}
}
}
const config = {
type: Phaser.AUTO,
width: 1024,
height: 768,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ассетов
Вся логика примера содержится в классе сцены Example. На этапе preload мы загружаем одно изображение — текстуру для наших частиц.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ball', 'assets/particles/sparkle1.png');
}
Метод setBaseURL задаёт базовый URL для загрузки, что позволяет указывать только относительные пути к ресурсам. В данном случае загружается изображение 'ball' — маленькая светящаяся частица в формате PNG, что идеально подходит для эффектов с наложением.
Определение пути с помощью кривой Spline
В методе create мы определяем траекторию движения. Для этого создаётся кривая типа Phaser.Curves.Spline. Она позволяет построить плавную кривую, проходящую через заданные контрольные точки.
const points = [
50, 300, 179, 449, 394, 498, 593, 455,
701, 338, 692, 190, 603, 76, 423, 41,
272, 78, 181, 186, 230, 328, 416, 395,
565, 327, 550, 202, 467, 149, 355, 164,
343, 254, 428, 303
];
const curve = new Phaser.Curves.Spline(points);
Массив points содержит пары координат [x1, y1, x2, y2, ...], которые определяют форму сложной, похожей на цветок, замкнутой фигуры. Кривая Spline автоматически сглаживает переходы между этими точками, создавая идеальный путь для плавной анимации.
Создание и настройка последователей (Followers)
Затем в цикле создаются 20 объектов-последователей (Follower). Каждый из них привязывается к созданной кривой curve и использует текстуру 'ball'.
for (let i = 0; i < 20; i++)
{
const follower = this.add.follower(curve, 100, 100 + (30 * i), 'ball');
follower.setScale(0.5);
follower.setBlendMode(Phaser.BlendModes.ADD);
}
- this.add.follower(curve, x, y, texture) создаёт новый объект Follower. Параметры `xиy(100, 100 + (30 * i)) задают начальную позицию спрайта до старта анимации, но она будет проигнорирована при вызовеstartFollow. Смещение поyна30 * i` просто разбрасывает частицы по вертикали в начальный момент.
- setScale(0.5) уменьшает размер частицы вдвое.
- setBlendMode(Phaser.BlendModes.ADD) устанавливает аддитивное смешивание цветов. Это означает, что при наложении частиц друг на друга их цвета складываются, создавая эффект свечения и повышая визуальную яркость композиции.
Запуск анимации движения с задержками
Ключевой метод startFollow запускает движение объекта по кривой с заданными параметрами анимации.
follower.startFollow({
duration: 4500,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
_delay: i * 200,
delay: i * 100
});
- duration: Время в миллисекундах, за которое объект проходит всю кривую (4500 мс = 4.5 секунды).
- yoyo: true: После завершения движения объект начинает двигаться по траектории в обратном направлении.
- repeat: -1: Анимация повторяется бесконечно.
- ease: 'Sine.easeInOut': Функция плавности (easing), которая обеспечивает плавное ускорение и замедление в начале и конце движения, делая его более естественным.
- delay и _delay: Здесь есть важный нюанс. В коде указаны оба параметра. Согласно документации Phaser, корректным параметром для задержки старта анимации является delay. Параметр _delay (с подчёркиванием) в публичном API не документирован и может быть устаревшим или внутренним. Значение delay: i * 100 создаёт каскадный эффект: каждая следующая частица начинает движение на 100 мс позже предыдущей. Это формирует эффект плавной "змейки" или цепочки, а не одновременного движения всей группы.
Конфигурация игры
За пределами класса сцены определяется стандартная конфигурация игры config и создаётся её экземпляр.
const config = {
type: Phaser.AUTO,
width: 1024,
height: 768,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
- backgroundColor: '#000000' устанавливает чёрный фон, на котором аддитивные светящиеся частицы выглядят наиболее эффектно.
- parent: 'phaser-example' указывает ID HTML-элемента, в который будет встроен canvas игры. В реальном проекте этот элемент должен существовать на странице.
Что попробовать дальше
Этот пример демонстрирует мощь и простоту системы следования за кривыми в Phaser 3. Всего несколько строк кода позволяют создать сложный и привлекательный визуальный паттерн. Для экспериментов попробуйте изменить массив точек points, чтобы получить новую форму траектории (например, символ бесконечности или спираль). Замените 'ball' на спрайт корабля или врага, и вы получите готовую систему патрулирования. Поиграйте с параметрами ease (например, Power2), duration и delay, чтобы добиться совершенно разного характера движения — от резкого до плавно-волнообразного.
