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

При создании игр часто возникает задача визуально скрыть часть изображения или спрайта, чтобы создать эффект постепенного появления, разрушения или нестандартного перехода. 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('face', 'assets/pics/archmage-in-your-face.png');
        this.load.image('glass', 'assets/particles/glass.png');
    }

    create ()
    {
        // Add a face.
        this.bg = this.add.image(640, 360, 'face')
        .setScale(720 / 512);

        // Add an ellipse over the mouth.
        const ellipse = this.add.ellipse(640, 520, 180, 240, 0xff0000);

        // Add a particle emitter emitting shards.
        // This is aligned to the center of the ellipse.
        const emitter = this.add.particles(640, 520, 'glass', {
            rotate: { min: 0, max: 360 },
            speed: { min: 100, max: 150 },
            lifespan: 5000,
            quantity: 1
        });

        // Combine ellipse and particles in a container.
        const container = this.add.container(0, 0)
        .setVisible(false);
        container.add(ellipse);
        container.add(emitter);

        // Use container as a mask of the face.
        this.bg.enableFilters();
        this.bg.filters.external.addMask(container);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 1280,
    height: 720,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: Example
};

let game = new Phaser.Game(config);

Подготовка сцены и загрузка ресурсов

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

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('face', 'assets/pics/archmage-in-your-face.png');
    this.load.image('glass', 'assets/particles/glass.png');
}

Первое изображение — это фон, который мы будем маскировать. Второе — текстура осколка стекла, которая будет использоваться в эмиттере частиц. Обратите внимание на использование setBaseURL для удобства указания пути к ресурсам.

Создание визуальных элементов

В методе create мы создаем основное изображение и масштабируем его под размеры игрового окна.

this.bg = this.add.image(640, 360, 'face')
.setScale(720 / 512);

Затем создается первый компонент будущей маски — красный эллипс. Он будет статичной частью маски.

const ellipse = this.add.ellipse(640, 520, 180, 240, 0xff0000);

Далее создается второй, динамический компонент — эмиттер частиц, который испускает осколки стекла. Он создается в той же точке, что и эллипс.

const emitter = this.add.particles(640, 520, 'glass', {
    rotate: { min: 0, max: 360 },
    speed: { min: 100, max: 150 },
    lifespan: 5000,
    quantity: 1
});

Ключевые параметры эмиттера: lifespan определяет время жизни частицы в миллисекундах, а quantity — сколько частиц испускается за один вызов. Здесь quantity: 1 создает эффект появления отдельных осколков.

Объединение в контейнер и создание маски

Теперь нужно объединить эллипс и эмиттер в единый объект, который сможет выступать в роли маски. Для этого используется Container.

const container = this.add.container(0, 0)
.setVisible(false);
container.add(ellipse);
container.add(emitter);

Контейнеру сразу же устанавливается setVisible(false). Это важно, потому что сам контейнер как игровой объект нам не нужно отрисовывать на сцене — он будет использоваться только как данные для маски.

Финальный и самый важный шаг — применение контейнера в качестве внешней маски к основному изображению.

this.bg.enableFilters();
this.bg.filters.external.addMask(container);

Сначала для изображения this.bg активируется система фильтров методом enableFilters(). Затем к его внешним фильтрам (filters.external) добавляется маска методом addMask(), куда и передается наш контейнер. Визуально это означает, что область изображения, соответствующая эллипсу и летящим от него частицам, станет видимой, а все остальное будет скрыто.

Принцип работы маски на основе контейнера

Маска в Phaser работает по принципу «белое — видимо, черное — прозрачно». Когда в качестве маски используется контейнер, система рассматривает совокупную область всех его дочерних элементов (в нашем случае — эллипс и каждую частицу) как «белую» область. Все, что попадает в эту область, остается видимым на целевом изображении (this.bg). Все, что находится за ее пределами, становится невидимым.

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

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

Использование контейнеров в качестве масок открывает путь к созданию сложных визуальных эффектов в Phaser. Вы можете маскировать изображения не только статичными фигурами, но и анимированными системами частиц, текстом или другими спрайтами. Для экспериментов попробуйте заменить эллипс на другой графический объект, изменить параметры эмиттера на более интенсивные (quantity: 10, lifespan: 1000) или применить такую же маску к интерактивному спрайту, чтобы создать эффект его постепенного разрушения.