О чем этот пример

Создание визуальных эффектов — ключевая часть гейм-дева. Частицы могут оживить окружение, придать удару вес или нарисовать след от магического заклинания. Часто нужно, чтобы частицы появлялись не просто в одной точке, а вдоль определённой линии или контура. В этой статье мы разберём, как использовать `addEmitZone` в Phaser для создания сложных и интересных паттернов эмиссии частиц, основанных на стандартных геометрических фигурах. Вы научитесь контролировать, откуда именно рождаются частицы, что сразу же повысит визуальную уникальность ваших эффектов.

Версия 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.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
    }

    create ()
    {
        const shape1 = new Phaser.Geom.Circle(0, 0, 160);
        const shape2 = new Phaser.Geom.Ellipse(0, 0, 500, 150);
        const shape3 = new Phaser.Geom.Rectangle(-150, -150, 300, 300);
        const shape4 = new Phaser.Geom.Line(-150, -150, 150, 150);
        const shape5 = new Phaser.Geom.Triangle.BuildEquilateral(0, -140, 300);

        const emitter = this.add.particles(400, 300, 'flares', {
            frame: { frames: [ 'red', 'green', 'blue', 'white', 'yellow' ], cycle: true },
            blendMode: 'ADD',
            lifespan: 500,
            quantity: 4,
            scale: { start: 0.5, end: 0.1 }
        });

        emitter.addEmitZone({ type: 'edge', source: shape1, quantity: 64, total: 1 });
        emitter.addEmitZone({ type: 'edge', source: shape2, quantity: 64, total: 1 });
        emitter.addEmitZone({ type: 'edge', source: shape3, quantity: 64, total: 1 });
        emitter.addEmitZone({ type: 'edge', source: shape4, quantity: 64, total: 1 });
        emitter.addEmitZone({ type: 'edge', source: shape5, quantity: 64, total: 1 });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Загрузка ресурсов и создание фигур

В методе preload загружается фон и атлас частиц. Атлас flares содержит несколько спрайтов разных цветов, которые мы будем использовать для эффекта.

Основная работа происходит в create. Первым делом мы создаём пять различных геометрических фигур, используя встроенные в Phaser классы геометрии. Важно отметить, что координаты для этих фигур задаются относительно их центра (точки 0, 0). Это упрощает их последующее позиционирование.

const shape1 = new Phaser.Geom.Circle(0, 0, 160);
const shape2 = new Phaser.Geom.Ellipse(0, 0, 500, 150);
const shape3 = new Phaser.Geom.Rectangle(-150, -150, 300, 300);
const shape4 = new Phaser.Geom.Line(-150, -150, 150, 150);
const shape5 = new Phaser.Geom.Triangle.BuildEquilateral(0, -140, 300);

Создание и настройка эмиттера частиц

Далее мы создаём сам эмиттер с помощью this.add.particles. Его начальная позиция задаётся координатами (400, 300) — это центр сцены. Эмиттер будет использовать текстуру flares.

Конфигурационный объект позволяет тонко настроить поведение частиц: - frame: Задаёт кадры из атласа. Массив ['red', 'green', 'blue', 'white', 'yellow'] и флаг cycle: true означают, что частицы будут циклически появляться всех этих цветов по очереди. - blendMode: 'ADD': Режим наложения, который делает яркие частицы ещё ярче, что идеально для эффектов вроде огня или вспышек. - lifespan: 500: Время жизни частицы в миллисекундах. - quantity: 4: Количество частиц, испускаемых за один выброс. - scale: Позволяет частицам уменьшаться с 0.5 до 0.1 за время их жизни.

const emitter = this.add.particles(400, 300, 'flares', {
    frame: { frames: [ 'red', 'green', 'blue', 'white', 'yellow' ], cycle: true },
    blendMode: 'ADD',
    lifespan: 500,
    quantity: 4,
    scale: { start: 0.5, end: 0.1 }
});

Добавление зон эмиссии с помощью addEmitZone

Самая важная часть — метод emitter.addEmitZone(). Он добавляет к эмиттеру зону, из которой будут появляться частицы. В нашем примере мы добавляем пять таких зон, по одной для каждой созданной ранее фигуры.

Параметры, передаваемые в метод, определяют поведение зоны: - type: 'edge': Указывает, что частицы должны появляться вдоль границы (ребра) фигуры, а не внутри неё. Это создаёт эффект контура. - source: Сама геометрическая фигура, которая служит источником для зоны. - quantity: 64: Это общее количество точек, которые будут сгенерированы вдоль границы фигуры. Чем больше значение, тем более равномерным будет распределение частиц по контуру (например, для круга). - total: 1: Количество частиц, которые будут испущены из этой зоны за один цикл. В нашем случае каждая зона испускает по одной частице за раз.

Эмиттер автоматически циклически перебирает все добавленные зоны, создавая частицы то из одной, то из другой.

emitter.addEmitZone({ type: 'edge', source: shape1, quantity: 64, total: 1 });
emitter.addEmitZone({ type: 'edge', source: shape2, quantity: 64, total: 1 });
emitter.addEmitZone({ type: 'edge', source: shape3, quantity: 64, total: 1 });
emitter.addEmitZone({ type: 'edge', source: shape4, quantity: 64, total: 1 });
emitter.addEmitZone({ type: 'edge', source: shape5, quantity: 64, total: 1 });

Что попробовать дальше

Использование addEmitZone открывает огромные возможности для создания нестандартных визуальных эффектов. Вместо статичного облака частиц вы получаете динамичные узоры, очерчивающие любую геометрическую форму. Для экспериментов попробуйте: 1. Изменить type на 'random', чтобы частицы появлялись в случайной точке *внутри* фигуры. 2. Поиграть с параметром total, заставляя зону испускать несколько частиц одновременно. 3. Создать свою сложную фигуру с помощью Phaser.Geom.Polygon и использовать её как источник. 4. Динамически менять source зоны в runtime, чтобы форма эффекта плавно трансформировалась.