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

Визуальные эффекты — ключевой элемент погружения в игре. Один из мощных, но малоизвестных инструментов Phaser 3 — фильтр `CombineColorMatrixFilter`. Он позволяет использовать яркость одной текстуры в качестве альфа-канала для другой, создавая сложные, динамические маски и эффекты наложения. В этой статье мы разберем практический пример, показывающий, как с помощью этого фильтра управлять прозрачностью двух изображений на основе третьего и анимировать результат.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    g1;
    g2;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('distortion', 'assets/textures/distortion7.png');
        this.load.image('gradient1', 'assets/skies/gradient1.png');
        this.load.image('gradient2', 'assets/skies/gradient2.png');
    }

    create ()
    {
        const bg = this.add.image(640, 360, 'distortion').setAlpha(0.1);
        Phaser.Actions.FitToRegion(bg, 1);

        // Use brightness from `distortion` as an alpha channel.
        const g1 = this.add.image(640, 360, 'gradient1');
        const g1Combine = g1.enableFilters().filters.internal.addCombineColorMatrix('distortion');
        g1Combine.setupAlphaTransfer(true, false, undefined, true);
        this.g1 = g1;

        // Invert the brightness-derived alpha.
        const g2 = this.add.image(640, 360, 'gradient2');
        const g2Combine = g2.enableFilters().filters.internal.addCombineColorMatrix('distortion');
        g2Combine.setupAlphaTransfer(true, false, undefined, undefined, undefined, true);
        this.g2 = g2;
    }

    update (time, delta)
    {
        const d = 128 * Math.sin(time / 3000);
        this.g1.x = 640 + d;
        this.g2.x = 640 - d;
    }
}

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

const game = new Phaser.Game(config);

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

Как и в любом проекте на Phaser, работа начинается с загрузки ресурсов. В методе preload мы указываем базовый URL и загружаем три текстуры: distortion, gradient1 и gradient2. Текстура distortion будет выступать в роли карты яркости (или альфа-карты) для двух градиентов.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('distortion', 'assets/textures/distortion7.png');
this.load.image('gradient1', 'assets/skies/gradient1.png');
this.load.image('gradient2', 'assets/skies/gradient2.png');

Создание фона и базовых изображений

В методе create мы сначала добавляем на сцену текстуру distortion в качестве фона. С помощью setAlpha(0.1) мы делаем её едва заметной, чтобы она не отвлекала от основного эффекта. Метод Phaser.Actions.FitToRegion растягивает изображение на всю сцену.

Затем создаются два основных изображения с градиентами (gradient1 и gradient2). Они будут центрированы на экране и станут носителями нашего визуального эффекта.

const bg = this.add.image(640, 360, 'distortion').setAlpha(0.1);
Phaser.Actions.FitToRegion(bg, 1);

const g1 = this.add.image(640, 360, 'gradient1');
const g2 = this.add.image(640, 360, 'gradient2');

Применение фильтра CombineColorMatrixFilter

Это ключевой этап. Для изображения g1 мы включаем систему фильтров с помощью enableFilters(). Затем, обращаясь к внутреннему менеджеру фильтров filters.internal, добавляем фильтр типа addCombineColorMatrix. В его конструктор передается ключ текстуры distortion. Теперь этот фильтр «знает» о карте яркости.

Метод setupAlphaTransfer настраивает, как именно яркость текстуры-источника будет преобразована в альфа-канал целевого изображения. Первый параметр true включает использование красного канала источника (в оттенках серого R=G=B, поэтому подойдет любой). Следующие параметры управляют инверсией. Для g1 мы передаем true в последний параметр, что означает: *использовать яркость как есть*. Для g2 мы передаем true в предпоследний параметр, что *инвертирует* полученную альфу.

const g1Combine = g1.enableFilters().filters.internal.addCombineColorMatrix('distortion');
g1Combine.setupAlphaTransfer(true, false, undefined, true);

const g2Combine = g2.enableFilters().filters.internal.addCombineColorMatrix('distortion');
g2Combine.setupAlphaTransfer(true, false, undefined, undefined, undefined, true);

Анимация результата

Чтобы эффект не был статичным, мы добавляем простую анимацию в методе update. На основе синусоидальной функции от времени вычисляется смещение `d. Затем мы двигаем два наших изображения с градиентами в противоположные стороны. Так как их прозрачность привязана к одной и той же текстуреdistortion`, но с инвертированной альфой для второго, создается эффект плавного перетекания одного изображения в другое.

update (time, delta)
{
    const d = 128 * Math.sin(time / 3000);
    this.g1.x = 640 + d;
    this.g2.x = 640 - d;
}

Конфигурация игры

Пример использует контекст рендеринга WEBGL, так как фильтры требуют поддержки WebGL. Задаются размеры окна, цвет фона и указывается класс сцены.

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

const game = new Phaser.Game(config);

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

Фильтр CombineColorMatrixFilter открывает тонкий контроль над наложением изображений, позволяя создавать эффекты маскирования, плавных переходов и динамических текстур на основе яркости. Для экспериментов попробуйте: использовать разные текстуры в качестве источника альфы (например, шум Перлина или рендер сцены); анимировать не положение, а параметры самого фильтра; комбинировать этот фильтр с другими, например, с BlurFilter для создания эффекта расфокусировки на краях маски.