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

Создание сложных визуальных эффектов в играх часто требует точного контроля над размещением объектов. Особенно это касается систем частиц, которые по своей природе динамичны и не имеют фиксированного размера. В этой статье мы разберем пример использования метода `Phaser.Actions.FitToRegion` для упаковки эмиттеров частиц в заданные области экрана, даже когда их исходный размер неизвестен. Этот прием полезен для создания локализованных эффектов, таких как огонь в факелах, магические ауры или облака дыма в строго ограниченных зонах.

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

    create ()
    {
        this.createFlameRegion(200, 200, 100, 100);
        this.createFlameRegion(500, 150, 75, 75);
        this.createFlameRegion(600, 300, 50, 50);
        this.createFlameRegion(350, 450, 150, 150);
    }

    createFlameRegion (x, y, width, height)
    {
        const flame = this.add.particles(150, 550, 'flares',
        {
            frame: 'white',
            color: [ 0xfacc22, 0xf89800, 0xf83600, 0x9f0404 ],
            colorEase: 'quad.out',
            lifespan: 2400,
            angle: { min: -100, max: -80 },
            scale: { start: 0.70, end: 0, ease: 'sine.out' },
            speed: 100,
            advance: 2000,
            blendMode: 'ADD'
        });

        const region = { x, y, width, height };

        // Set the dimensions of the target manually, because ParticleEmitter doesn't have size.
        Phaser.Actions.FitToRegion(flame, -1, region, { width: 64, height: 64 });
    }
}

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

const game = new Phaser.Game(config);

Суть проблемы: эмиттер частиц не имеет размера

В Phaser 3 эмиттер частиц, создаваемый через this.add.particles, является контейнером для управления системой частиц. Ключевая особенность в том, что сам эмиттер как игровой объект не имеет свойств ширины и высоты (width, height). Его размер определяется исключительно позицией испускания и параметрами частиц (скоростью, углом, временем жизни).

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

В примере для решения этой проблемы используется метод Phaser.Actions.FitToRegion.

Разбираем метод createFlameRegion

Вся логика создания эффекта заключена в методе createFlameRegion. Он принимает координаты и размеры целевой области и создает в ней локализованное пламя.

Сначала создается эмиттер частиц с определенными настройками. Обратите внимание: его начальная позиция (150, 550) в данном контексте не важна, так как позже мы применим действие для его перерасчета.

const flame = this.add.particles(150, 550, 'flares',
{
    frame: 'white',
    color: [ 0xfacc22, 0xf89800, 0xf83600, 0x9f0404 ],
    colorEase: 'quad.out',
    lifespan: 2400,
    angle: { min: -100, max: -80 },
    scale: { start: 0.70, end: 0, ease: 'sine.out' },
    speed: 100,
    advance: 2000,
    blendMode: 'ADD'
});

Затем создается объект region, описывающий целевую область, куда нужно "вписать" эмиттер.

const region = { x, y, width, height };

Магия Phaser.Actions.FitToRegion

Ключевой вызов происходит здесь. Phaser.Actions.FitToRegion — это статический метод, который применяет трансформации к списку игровых объектов, чтобы они поместились в указанный регион.

Phaser.Actions.FitToRegion(flame, -1, region, { width: 64, height: 64 });

Разберем аргументы: 1. flame: Целевой эмиттер частиц (или массив объектов). 2. -1: Индекс, с которого начинать обработку в массиве. Значение -1 означает "обработать все объекты". Поскольку flame — это один объект, а не массив, этот параметр игнорируется. 3. region: Объект с полями `x,y,width,height`, описывающий целевую область. 4. { width: 64, height: 64 }: **Самый важный аргумент.** Это объект с предполагаемыми (known) размерами исходного эмиттера. Поскольку у эмиттера нет реальных width и height, мы должны задать их виртуально. Метод использует эти значения как базовый размер объекта, а затем масштабирует и перемещает его так, чтобы эти виртуальные 64x64 пикселя идеально вписались в заданный region.

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

Как это работает на практике

В методе create сцена создает четыре области пламени с разными размерами и позициями.

create ()
{
    this.createFlameRegion(200, 200, 100, 100);
    this.createFlameRegion(500, 150, 75, 75);
    this.createFlameRegion(600, 300, 50, 50);
    this.createFlameRegion(350, 450, 150, 150);
}

Несмотря на то что все эмиттеры изначально создаются в одной точке (150, 550), после вызова FitToRegion каждый из них оказывается размещенным и отмасштабированным внутри своего региона.

Эффект пламени, состоящий из множества частиц, будет визуально ограничен прямоугольником региона. Если регион маленький (50x50), пламя будет компактным. Если регион большой (150x150), то же самое пламя растянется, заполняя всю область, сохраняя при этом свои пропорции и динамику.

Выбор виртуального размера (knownRect)

Параметр knownRect ({ width: 64, height: 64 }) — это творческий выбор разработчика. Его значение влияет на итоговый масштаб эффекта.

* Если задать слишком маленький knownRect (например, 10x10), метод попытается "растянуть" этот маленький виртуальный объект до размеров региона. В результате исходный эффект (пламя) будет сильно увеличен, и частицы могут выглядеть разреженно или, наоборот, сгруппированно. * Если задать слишком большой knownRect (например, 200x200), эффект, наоборот, сожмется, чтобы вписаться в регион, и может стать слишком плотным.

Значение 64x64 в примере подобрано эмпирически для создания гармоничного визуального эффекта пламени в регионах указанных размеров. Вам стоит экспериментировать с этим значением для каждого конкретного эффекта и желаемого визуального результата.

// Эксперимент: более плотное или разреженное пламя
Phaser.Actions.FitToRegion(flame, -1, region, { width: 32, height: 32 }); // Эффект будет увеличен
Phaser.Actions.FitToRegion(flame, -1, region, { width: 96, height: 96 }); // Эффект будет уменьшен

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

Phaser.Actions.FitToRegion — это мощный инструмент для точного позиционирования и масштабирования игровых объектов, особенно тех, которые не имеют явного размера, как эмиттеры частиц. Он позволяет декларативно задавать область для визуального эффекта, перекладывая сложные расчеты трансформаций на движок. **Идеи для экспериментов:** 1. Используйте этот метод не только для частиц, но и для групп спрайтов (Phaser.GameObjects.Group), чтобы ровно размещать наборы объектов в интерфейсе или на игровом поле. 2. Попробуйте анимировать параметры региона (`x,y,width,height`) с помощью твинов, чтобы создавать эффекты "расширяющегося взрыва" или "движущегося облака". 3. Комбинируйте FitToRegion с другими действиями, например PlaceOnCircle, чтобы размещать эмиттеры по сложным траекториям, но с контролем над их итоговым масштабом.