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

Часто в играх нужно управлять видимостью целой группы объектов не по отдельности, а всем скопом. Например, чтобы герои появлялись из тумана, магический эффект постепенно раскрывал врагов или, как в нашем примере, стадо слонов выходило из кляксы. Обычные маски для спрайтов работают точечно, но что если объектов десятки? В Phaser 3 для этого есть мощный инструмент — фильтры и внешние маски на слоях (Layers). Эта статья покажет, как с помощью всего нескольких строк кода привязать одну битмап-маску ко всем объектам слоя, создав стильный и динамичный визуальный эффект.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('elephant', 'assets/sprites/elephant.png');
        this.load.image('splat', 'assets/pics/splat1.png');
    }

    create ()
    {
        const elephantLayer = this.add.layer();

        const splat = this.make.image({ x: 400, y: 300, key: 'splat' }, false);

        elephantLayer.enableFilters().filters.external.addMask(splat);

        for (let i = 0; i < 64; i++)
        {
            let x = Phaser.Math.Between(600, 800);
            let y = Phaser.Math.Between(0, 600);

            let sprite = elephantLayer.add(this.make.sprite({ x, y, key: 'elephant' }));

            let dx = x - 600;

            this.tweens.add({
                targets: sprite,
                x: dx,
                ease: 'Sine.inOut',
                duration: 4000,
                delay: (i * 50),
                yoyo: true,
                repeat: -1
            });
        }
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и создание слоя

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

Первым делом создаётся слой с помощью this.add.layer(). Слой (Layer) в Phaser — это контейнер для игровых объектов, который позволяет управлять ими как единой группой: применять трансформации, фильтры и, что самое важное для нас, общие маски.

const elephantLayer = this.add.layer();

Создание маски из изображения

Чтобы использовать изображение как маску, его нужно сначала создать как игровой объект. Мы используем фабрику this.make.image. Важный нюанс: второй параметр false указывает, что объект не должен быть автоматически добавлен на дисплейный список (не отрендерится сам по себе). Он нужен только как источник данных для маски.

const splat = this.make.image({ x: 400, y: 300, key: 'splat' }, false);

Теперь у нас есть объект splat, который содержит текстуру нашей кляксы. Его координаты (400, 300) задают центр маски на сцене.

Включение фильтров и применение маски к слою

Самый важный шаг — связать маску со слоем. Сначала необходимо активировать систему фильтров для слоя, вызвав метод enableFilters(). После этого у слоя становится доступно свойство filters, а в нём — массив external для внешних эффектов.

Метод addMask этого массива принимает наш объект splat и применяет его текстуру в качестве маски ко всему слою. Теперь любой объект, добавленный в elephantLayer, будет виден только в тех пикселях, где маска (splat) непрозрачна.

elephantLayer.enableFilters().filters.external.addMask(splat);

Наполнение слоя и анимация

Далее мы создаём 64 спрайта слона, используя фабрику this.make.sprite, и каждый сразу добавляем в наш слой с помощью elephantLayer.add(). Исходные позиции слонов генерируются случайно в правой части экрана.

let sprite = elephantLayer.add(this.make.sprite({ x, y, key: 'elephant' }));

Для каждого спрайта создаётся твин (анимация перемещения) с помощью this.tweens.add. Слоны двигаются по горизонтали от своей начальной позиции (`x) до точкиdx(рассчитанной относительно левого края), а затем обратно. Параметрыyoyo: trueиrepeat: -1заставляют анимацию бесконечно повторяться вперёд и назад. Задержка (delay`) для каждого следующего слона создаёт волнообразный эффект движения.

this.tweens.add({
    targets: sprite,
    x: dx,
    ease: 'Sine.inOut',
    duration: 4000,
    delay: (i * 50),
    yoyo: true,
    repeat: -1
});

Итог: всё стадо слонов движется туда-сюда, но видно мы их только внутри контура кляксы-маски, создавая иллюзию, что они появляются из неё.

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

Использование внешних масок на слоях — это мощный и производительный способ применять сложные эффекты видимости к большим группам объектов. Вместо того чтобы накладывать маску на каждый спрайт по отдельности (что ресурсозатратно), мы делаем это один раз для всего контейнера. Вы можете экспериментировать: анимировать саму маску (менять её `x,y,scale`), использовать в качестве маски не статичную картинку, а спрайт-лист (с анимацией), или комбинировать несколько слоёв с разными масками для создания сложных композиций. Этот приём отлично подходит для создания переходов между уровнями, раскрытия карты или магических заклинаний, влияющих на видимость множества целей.