О чем этот пример
Визуальные эффекты, такие как фейерверки или магические следы, часто требуют, чтобы частицы двигались не по прямой, а по сложной траектории. В этой статье мы разберем пример, который показывает, как привязать эмиттеры частиц к точкам кривой Безье, создавая управляемый и визуально привлекательный поток искр. Этот подход полезен для создания анимаций выстрелов, заклинаний, праздничных эффектов и любых других систем, где нужно точно контролировать исходное положение и направление частиц.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.55.2.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('spark0', 'assets/particles/blue.png');
this.load.image('spark1', 'assets/particles/red.png');
this.load.image('logo', 'assets/sprites/phaser2.png');
}
create ()
{
const p0 = new Phaser.Math.Vector2(200, 500);
const p1 = new Phaser.Math.Vector2(200, 200);
const p2 = new Phaser.Math.Vector2(600, 200);
const p3 = new Phaser.Math.Vector2(600, 500);
const curve = new Phaser.Curves.CubicBezier(p0, p1, p2, p3);
const max = 28;
const points = [];
const tangents = [];
for (let c = 0; c <= max; c++)
{
const t = curve.getUtoTmapping(c / max);
points.push(curve.getPoint(t));
tangents.push(curve.getTangent(t));
}
const tempVec = new Phaser.Math.Vector2();
const spark0 = this.add.particles('spark0');
const spark1 = this.add.particles('spark1');
for (let i = 0; i < points.length; i++)
{
const p = points[i];
tempVec.copy(tangents[i]).normalizeRightHand().scale(-32).add(p);
const angle = Phaser.Math.RadToDeg(Phaser.Math.Angle.BetweenPoints(p, tempVec));
const particles = (i % 2 === 0) ? spark0 : spark1;
particles.createEmitter({
x: tempVec.x,
y: tempVec.y,
angle: angle,
speed: { min: -100, max: 500 },
gravityY: 200,
scale: { start: 0.4, end: 0.1 },
lifespan: 800,
blendMode: 'SCREEN'
});
}
this.add.image(400, 400, 'logo');
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ассетов
Класс Example расширяет Phaser.Scene. В методе preload() мы загружаем три изображения: две текстуры для частиц разных цветов (spark0 и spark1) и логотип для фона. Базовый URL задается для удобства, чтобы указывать относительные пути к файлам примеров с GitHub.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('spark0', 'assets/particles/blue.png');
this.load.image('spark1', 'assets/particles/red.png');
this.load.image('logo', 'assets/sprites/phaser2.png');
}
Построение кривой и расчет точек
В методе create() сначала определяется форма нашего фейерверка — кубическая кривая Безье. Для ее создания нужны четыре опорные точки (вектора p0, p1, p2, p3), которые задают начальную, конечную и две контрольные точки.
const p0 = new Phaser.Math.Vector2(200, 500);
const p1 = new Phaser.Math.Vector2(200, 200);
const p2 = new Phaser.Math.Vector2(600, 200);
const p3 = new Phaser.Math.Vector2(600, 500);
const curve = new Phaser.Curves.CubicBezier(p0, p1, p2, p3);
Затем мы разбиваем кривую на сегменты. Количество сегментов (max) определяет плотность размещения эмиттеров. Для каждого сегмента вычисляется позиция точки на кривой (getPoint) и вектор касательной (getTangent). Касательная — это направление кривой в данной точке, которое позже будет использовано для задания угла разлета частиц.
const max = 28;
const points = [];
const tangents = [];
for (let c = 0; c <= max; c++)
{
const t = curve.getUtoTmapping(c / max);
points.push(curve.getPoint(t));
tangents.push(curve.getTangent(t));
}
Создание систем частиц и настройка эмиттеров
Создаются две системы частиц (ParticleManager), по одной для каждой текстуры. Система управляет всеми эмиттерами, использующими ее текстуру.
const spark0 = this.add.particles('spark0');
const spark1 = this.add.particles('spark1');
Теперь ключевой шаг: для каждой рассчитанной точки на кривой создается отдельный эмиттер. Чтобы частицы вылетали не из самой точки на кривой, а с небольшым смещением наружу (создавая объем), используется вектор tempVec. Он копирует касательную, нормализует ее, поворачивает на 90 градусов вправо (normalizeRightHand), масштабирует и добавляет к исходной точке. Угол (angle) для эмиттера вычисляется между исходной точкой и этой смещенной точкой, что заставляет частицы вылетать перпендикулярно кривой.
const tempVec = new Phaser.Math.Vector2();
for (let i = 0; i < points.length; i++)
{
const p = points[i];
tempVec.copy(tangents[i]).normalizeRightHand().scale(-32).add(p);
const angle = Phaser.Math.RadToDeg(Phaser.Math.Angle.BetweenPoints(p, tempVec));
// ...
}
Конфигурация эмиттеров частиц
Цвет частиц чередуется: для четных точек используется система spark0 (синие), для нечетных — spark1 (красные). Каждый эмиттер настраивается с помощью конфигурационного объекта.
const particles = (i % 2 === 0) ? spark0 : spark1;
particles.createEmitter({
x: tempVec.x,
y: tempVec.y,
angle: angle,
speed: { min: -100, max: 500 },
gravityY: 200,
scale: { start: 0.4, end: 0.1 },
lifespan: 800,
blendMode: 'SCREEN'
});
Важные параметры:
- speed: широкий разброс (min: -100, max: 500) создает хаотичный, но направленный взрыв.
- gravityY: придает частицам вес, заставляя их падать вниз, как искры фейерверка.
- scale: уменьшение размера от 0.4 до 0.1 создает эффект затухания.
- lifespan: время жизни частицы в миллисекундах.
- blendMode: 'SCREEN' делает яркие цвета частиц светящимися на темном фоне.
Запуск сцены и итоговый конфиг
В конце сцены добавляется фоновое изображение логотипа. Основная конфигурация игры (config) задает тип рендерера, размеры холста, цвет фона и корневой класс сцены.
this.add.image(400, 400, 'logo');
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Мы разобрали, как создать анимацию фейерверка, жестко привязав множество эмиттеров частиц к точкам кривой Безье. Это дает полный контроль над формой и направлением эффекта. Для экспериментов попробуйте: изменить форму кривой, перемещая опорные точки; использовать другие типы кривых из Phaser (QuadraticBezier, Ellipse); анимировать опорные точки во времени, чтобы фейерверк "двигался"; или заменить текстуры частиц на спрайты с несколькими кадрами для более сложных эффектов.
