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

Создание визуальных эффектов — ключевая часть геймдева, и система частиц Phaser предлагает мощные инструменты для этого. Стандартный эмиттер часто выпускает частицы из одной точки, но что, если нужно заполнить частицами контур сложной фигуры? В этой статье мы разберем пример использования зон испускания (Emit Zones) для генерации частиц из геометрических фигур: кругов, эллипсов, прямоугольников, линий и треугольников. Этот подход позволит вам легко создавать эффекты магических кругов, силовых полей, лазерных лучей и многое другое, напрямую связывая физику частиц с геометрией вашего мира.

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

    create ()
    {
        const emitter = this.add.particles('spark').createEmitter({
            x: 400,
            y: 300,
            blendMode: 'SCREEN',
            scale: { start: 0.2, end: 0 },
            speed: { min: -100, max: 100 },
            quantity: 50
        });

        const emitZones = [];

        emitZones.push({
            source: new Phaser.Geom.Circle(0, 0, 100),
            type: 'edge',
            quantity: 50
        });
        emitZones.push({
            source: new Phaser.Geom.Ellipse(0, 0, 400, 100),
            type: 'edge',
            quantity: 50
        });
        emitZones.push({
            source: new Phaser.Geom.Rectangle(-150, -150, 300, 300),
            type: 'edge',
            quantity: 50
        });
        emitZones.push({
            source: new Phaser.Geom.Line(-150, -150, 150, 150),
            type: 'edge',
            quantity: 50
        });
        emitZones.push({
            source: new Phaser.Geom.Triangle(0, -200, 200, 200, -200, 200),
            type: 'edge',
            quantity: 50
        });

        let emitZoneIndex = 0;

        this.input.on('pointermove', pointer =>
        {
            emitter.setPosition(pointer.x, pointer.y);
        });

        this.input.on('pointerdown', pointer =>
        {
            emitZoneIndex = (emitZoneIndex + 1) % emitZones.length;
            emitter.setEmitZone(emitZones[emitZoneIndex]);
            emitter.explode();
        });

        emitter.setEmitZone(emitZones[emitZoneIndex]);
    }
}

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

const game = new Phaser.Game(config);

Настройка базового эмиттера частиц

Всё начинается с создания обычного эмиттера частиц. В методе create() сцены мы инициализируем эмиттер, используя заранее загруженную текстуру частицы.

const emitter = this.add.particles('spark').createEmitter({
    x: 400,
    y: 300,
    blendMode: 'SCREEN',
    scale: { start: 0.2, end: 0 },
    speed: { min: -100, max: 100 },
    quantity: 50
});
Ключевые параметры здесь:
*   `blendMode: 'SCREEN'` — задаёт режим наложения, при котором светлые частицы "осветляют" фон, создавая эффект свечения.
*   `scale: { start: 0.2, end: 0 }` — каждая частица плавно уменьшается от 20% размера до полного исчезновения.
*   `speed: { min: -100, max: 100 }` — частицам задаётся случайный вектор скорости в диапазоне от -100 до 100 пикселей в секунду по обеим осям, создавая хаотичное движение.
*   `quantity: 50` — количество частиц, испускаемых за один вызов. Эмиттер пока не активирован, частицы будут появляться только по команде.

Создание библиотеки геометрических зон испускания

Сердце примера — массив emitZones. Каждый его элемент является конфигурационным объектом для зоны испускания. Зона определяет, *откуда* и *как* появляются новые частицы.

const emitZones = [];

emitZones.push({
    source: new Phaser.Geom.Circle(0, 0, 100),
    type: 'edge',
    quantity: 50
});
// ... аналогично добавляются Ellipse, Rectangle, Line, Triangle

Свойства зоны: * source — экземпляр геометрической фигуры из модуля Phaser.Geom. Координаты фигуры задаются относительно позиции эмиттера. * type: 'edge' — указывает, что частицы должны появляться строго на границе (контуре) фигуры. Альтернатива — 'random', для случайного распределения внутри фигуры. * quantity — переопределяет глобальную настройку эмиттера для этой зоны. Здесь 50 частиц будут равномерно распределены по контуру выбранной фигуры.

Управление эмиттером и переключение фигур

Пример добавляет два типа интерактивности: перемещение источника эффекта и смену фигуры, из которой испускаются частицы.

this.input.on('pointermove', pointer => {
    emitter.setPosition(pointer.x, pointer.y);
});

При движении мыши позиция всего эмиттера (и, как следствие, всех его зон испускания) обновляется методом setPosition(). Это заставляет облако частиц следовать за курсором.

let emitZoneIndex = 0;

this.input.on('pointerdown', pointer => {
    emitZoneIndex = (emitZoneIndex + 1) % emitZones.length;
    emitter.setEmitZone(emitZones[emitZoneIndex]);
    emitter.explode();
});

По клику мыши происходит три действия: 1. Индекс текущей зоны циклически переключается на следующую в массиве. 2. Метод emitter.setEmitZone() применяет новую, выбранную по индексу, конфигурацию зоны к эмиттеру. 3. Метод emitter.explode() мгновенно испускает ровно то количество частиц, которое указано в активной зоне (50), размещая их по её контуру. Это создаёт мгновенную вспышку частиц в форме фигуры.

Практические советы и применение

Этот паттерн открывает множество возможностей для игровых эффектов.

// Пример: создание статичного энергетического щита в форме шестиугольника
const hexagon = new Phaser.Geom.Polygon([
    new Phaser.Geom.Point(0, -80),
    new Phaser.Geom.Point(69, -40),
    new Phaser.Geom.Point(69, 40),
    new Phaser.Geom.Point(0, 80),
    new Phaser.Geom.Point(-69, 40),
    new Phaser.Geom.Point(-69, -40)
]);

const shieldEmitter = this.add.particles('energy').createEmitter({
    lifespan: 1000,
    scale: { start: 0.5, end: 0 },
    blendMode: 'ADD',
    emitZone: { source: hexagon, type: 'edge', quantity: 20 },
    frequency: 100 // Испускает 20 частиц каждые 100 мс по контуру
});
shieldEmitter.start();

Идеи для использования: * **Линия (Line)**: идеальна для эффектов лазерного луча или молнии. * **Эллипс (Ellipse)**: можно создать эффект орбиты или энергетического кольца вокруг объекта. * **Прямоугольник (Rectangle) с типом 'random'**: заполнение дымом или искрами внутри заданной области (например, разрушаемого блока). Главное — помнить, что координаты фигуры-источника (source) задаются относительно позиции эмиттера, что позволяет легко привязывать сложные эффекты к движущимся игровым объектам.

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

Использование EmitZone с геометрическими фигурами — это мощный приём для выхода за рамки точечных эффектов. Вы можете проектировать визуалы, напрямую соответствующие форме игровых объектов или зон. Для экспериментов попробуйте: комбинировать несколько эмиттеров с разными фигурами в одном эффекте; анимировать саму фигуру-источник (например, вращая или масштабируя её) и обновлять зону эмиттера в update(); использовать тип 'random' для заполнения частицами площади сложного полигона, создавая эффекты тумана или скопления звёзд.