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

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

Версия 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('walker', 'assets/animations/walker.png', 'assets/animations/walker.json');
        this.load.image('sky', 'assets/skies/ms3-sky.png');
        this.load.image('trees', 'assets/skies/ms3-trees.png');
    }

    create ()
    {
        this.bg = this.add.tileSprite(0, 158, 1280, 296, 'sky')
        .setOrigin(0, 0);
        this.trees = this.add.tileSprite(0, 400, 1280, 320, 'trees')
        .setOrigin(0, 0);

        const animConfig = {
            key: 'walk',
            frames: 'walker',
            frameRate: 60,
            repeat: -1
        };

        this.anims.create(animConfig);

        const sprite = this.add.sprite(640, 120, 'walker', 'frame_0000')
        .setVisible(false);

        sprite.play('walk');

        // Create a band of clouds.
        this.clouds = this.add.tileSprite(0, 484, 1280, 296, 'sky')
        .setOrigin(0, 0)
        .enableFilters();
        const cloudFiltersInternal = this.clouds.filters.internal;
        cloudFiltersInternal.addPixelate(6);
        cloudFiltersInternal.addMask(sprite);
        cloudFiltersInternal.addBlur(0, 1, 1, 1);
    }

    update ()
    {
        this.bg.tilePositionX -= 2;
        this.trees.tilePositionX -= 6;
        this.clouds.tilePositionX += 1;
    }
}

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

let game = new Phaser.Game(config);

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

В методе preload загружаются необходимые ресурсы. Ключевой элемент — атлас walker с кадрами анимации персонажа. Также загружаются два фоновых изображения: небо (sky) и деревья (trees), которые будут использоваться как тайл-спрайты для создания параллакс-эффекта.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.atlas('walker', 'assets/animations/walker.png', 'assets/animations/walker.json');
    this.load.image('sky', 'assets/skies/ms3-sky.png');
    this.load.image('trees', 'assets/skies/ms3-trees.png');
}

Создание анимации и маскирующего спрайта

В методе create сначала создаются фоновые слои параллакса — два тайл-спрайта. Затем конфигурируется и создается анимация walk для спрайта персонажа.

Сам спрайт-маска создается в позиции (640, 120) с начальным кадром frame_0000. Важный момент: спрайт делается невидимым (setVisible(false)), так как нам нужна только его текстура для маски, а не его отображение на сцене. После этого анимация проигрывается.

const animConfig = {
    key: 'walk',
    frames: 'walker',
    frameRate: 60,
    repeat: -1
};
this.anims.create(animConfig);

const sprite = this.add.sprite(640, 120, 'walker', 'frame_0000')
.setVisible(false);
sprite.play('walk');

Применение фильтров с маской

Создается третий тайл-спрайт clouds из текстуры неба. Для него активируется система фильтров методом enableFilters(). Далее работа идет с внутренним списком фильтров объекта: this.clouds.filters.internal.

К тайл-спрайту последовательно применяются три фильтра: 1. addPixelate(6) — делает изображение пиксельным. 2. addMask(sprite) — ключевой фильтр. В качестве маски передается наш анимированный спрайт. Теперь фильтры будут применяться только к тем частям clouds, которые попадают в непрозрачные области маски (силуэт идущего персонажа). 3. addBlur(0, 1, 1, 1) — добавляет небольшое вертикальное размытие.

this.clouds = this.add.tileSprite(0, 484, 1280, 296, 'sky')
.setOrigin(0, 0)
.enableFilters();
const cloudFiltersInternal = this.clouds.filters.internal;
cloudFiltersInternal.addPixelate(6);
cloudFiltersInternal.addMask(sprite);
cloudFiltersInternal.addBlur(0, 1, 1, 1);

В результате получается слой «облаков», который виден только сквозь силуэт идущего персонажа и имеет пиксельный, слегка размытый вид.

Анимация фона

В методе update реализуется простой параллакс-эффект за счет сдвига текстур тайл-спрайтов с разной скоростью. Фоновые слои (bg и trees) движутся влево, а слой с фильтром и маской (clouds) — вправо, создавая динамичную и живую сцену.

update ()
{
    this.bg.tilePositionX -= 2;
    this.trees.tilePositionX -= 6;
    this.clouds.tilePositionX += 1;
}

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

Использование спрайта в качестве маски для фильтров открывает широкие возможности для постобработки графики в Phaser. Вы можете маскировать не только тайл-спрайты, но и любые другие объекты с поддержкой фильтров. Поэкспериментируйте: используйте в качестве маски частицу (Particle), текстовый объект (Text) или тайлмап (TilemapLayer). Попробуйте комбинировать маску с другими фильтрами, например, addGlow или addBloom, чтобы создавать свечение вокруг анимированного силуэта. Можно менять свойства маски (размер, позицию, прозрачность) в реальном времени для реализации сложных переходов между сценами.