О чем этот пример
Эффекты частиц оживляют игру, но их привязка к статическим точкам часто выглядит неестественно. В этом примере мы покажем, как динамически менять источник эмиссии частиц, привязав его к границам игровых объектов. Этот подход позволяет создавать интерактивные эффекты, например, свечение вокруг карт при наведении, без ручного пересчёта координат. Вы научитесь создавать эмиттер с несколькими зонами испускания и мгновенно переключать его между ними в реальном времени. Это полезно для визуальных подсказок, анимации выделения и создания динамических атмосферных эффектов.
Версия 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('bg', 'assets/skies/darkstone.png');
this.load.image('flare', 'assets/particles/white-flare.png');
this.load.image('fox', 'assets/pics/card3.png');
this.load.image('lizard', 'assets/pics/card2.png');
}
create ()
{
this.add.image(400, 300, 'bg');
const card1 = this.add.image(225, 300, 'fox').setInteractive();
const card2 = this.add.image(575, 300, 'lizard').setInteractive();
const zone1 = new Phaser.GameObjects.Particles.Zones.EdgeZone(card1.getBounds(), 42);
const zone2 = new Phaser.GameObjects.Particles.Zones.EdgeZone(card2.getBounds(), 42);
const emitter = this.add.particles(0, 0, 'flare', {
speed: 24,
lifespan: 1500,
quantity: 5,
scale: { start: 0.4, end: 0 },
advance: 2000,
emitZone: [ zone1, zone2 ]
});
card1.on('pointerover', () => {
emitter.setEmitZone(zone1);
emitter.fastForward(2000);
});
card2.on('pointerover', () => {
emitter.setEmitZone(zone2);
emitter.fastForward(2000);
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Создание зон испускания на основе объектов
Ключевой элемент примера — создание зон испускания (emit zones), которые определяют, откуда будут появляться частицы. Вместо использования простых точек мы создаём зоны по контуру других игровых объектов (спрайтов).
Для этого используется класс Phaser.GameObjects.Particles.Zones.EdgeZone. Его конструктор принимает два аргумента: прямоугольную область (Phaser.Geom.Rectangle) и количество точек, равномерно распределённых по её периметру.
Здесь мы получаем прямоугольные границы каждой карты с помощью метода getBounds(). Это автоматически вычисляет актуальную область объекта на сцене.
const zone1 = new Phaser.GameObjects.Particles.Zones.EdgeZone(card1.getBounds(), 42);
const zone2 = new Phaser.GameObjects.Particles.Zones.EdgeZone(card2.getBounds(), 42);
Настройка эмиттера с несколькими зонами
Эмиттер частиц создаётся методом this.add.particles. Важная деталь — ему изначально можно передать массив зон в параметре конфигурации emitZone. Это означает, что при старте эмиссии частицы могут появляться из любой из этих зон.
Однако в нашем примере эмиттер изначально не активен (параметр quantity установлен, но нет команды start). Он будет активироваться позже, по событию.
Обратите внимание на параметр advance: 2000. Он «прокручивает» внутренний таймер эмиттера вперёд на 2000 миллисекунд при создании. Это нужно для того, чтобы эффект сразу выглядел зрелым, а не начинался с нуля.
const emitter = this.add.particles(0, 0, 'flare', {
speed: 24,
lifespan: 1500,
quantity: 5,
scale: { start: 0.4, end: 0 },
advance: 2000,
emitZone: [ zone1, zone2 ]
});
Динамическое переключение зон по событию
Интерактивность обеспечивается обработчиками событий pointerover на картах. При наведении курсора на объект мы меняем активную зону испускания у уже созданного эмиттера.
Метод emitter.setEmitZone() заменяет текущий набор зон на переданную. После этого все новые частицы будут появляться только из этой зоны.
Следом вызывается emitter.fastForward(2000). Этот метод аналогичен параметру advance, но применяется к уже работающему эмиттеру. Он мгновенно генерирует частицы так, как если бы эмиттер работал последние 2 секунды. Это создаёт мгновенный, насыщенный эффект свечения, а не плавное его появление.
card1.on('pointerover', () => {
emitter.setEmitZone(zone1);
emitter.fastForward(2000);
});
Почему это работает эффективно
Использование EdgeZone эффективнее, чем ручной расчёт точек по периметру. Движок сам равномерно распределяет заданное количество точек (в нашем случае 42) по границе прямоугольника.
Сочетание setEmitZone и fastForward — это паттерн для мгновенной визуальной обратной связи. Без fastForward эмиттер начал бы испускать 5 частиц за кадр (quantity: 5), и эффект набрал бы плотность лишь через несколько секунд. fastForward решает эту проблему, симулируя работу в прошлом.
Важно, что зоны создаются на основе getBounds(). Если объект card1 будет двигаться или анимироваться, его границы изменятся, но зона zone1 останется статичной, так как она была создана в конкретный момент времени. Для следования зоны за объектом потребуется обновлять её каждый кадр.
Что попробовать дальше
Вы научились создавать динамические источники для систем частиц, привязанные к другим объектам на сцене. Этот подход открывает путь к сложным интерактивным эффектам.
**Идеи для экспериментов:**
1. Создайте зону не по прямоугольнику, а по произвольному многоугольнику с помощью ShapeZone.
2. Заставьте зону следовать за движущимся объектом, пересоздавая её в update().
3. Используйте setEmitZone не с одной, а с массивом зон для более сложных паттернов эмиссии.
4. Поэкспериментируйте с параметрами fastForward и advance, чтобы контролировать «интенсивность» старта эффекта.
