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

Визуальные эффекты — мощный инструмент для создания атмосферы и выделения объектов в игре. В этом примере мы разберем, как с помощью фильтров 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('bg', 'assets/pics/fairy-background-craft-pixel.png');
        this.load.image('sprite', 'assets/rope/dragonball.png');
    }

    create ()
    {
        const bg = this.add.image(400, 200, 'bg').setScale(2);

        this.spriteBase = this.add.image(240, 420, 'sprite').setFlipX(true);
        this.spriteKey = this.add.image(1000, 400, 'sprite').enableFilters();

        // Isolate the blue uniform and turn it saffron.
        const parallelFilters = this.spriteKey.filters.internal.addParallelFilters();
        const keyRed = parallelFilters.top.addKey({
            color: 0x241cb4,
            isolate: true,
            threshold: 0.4
        });
        const colorMatrix = parallelFilters.top.addColorMatrix();
        colorMatrix.colorMatrix.hue(150).brightness(2,true);
    }
}

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

const game = new Phaser.Game(config);

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

В методе preload мы загружаем два изображения с удаленного сервера, установив базовый URL. Это фон и основной спрайт, с которым будем работать.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/pics/fairy-background-craft-pixel.png');
    this.load.image('sprite', 'assets/rope/dragonball.png');
}

В методе create мы размещаем фон и два экземпляра одного и того же спрайта. Первый спрайт (spriteBase) отображается в оригинале, а для второго (spriteKey) мы включаем возможность применения фильтров с помощью метода .enableFilters(). Это обязательный шаг перед добавлением любых фильтров к объекту.

create ()
{
    const bg = this.add.image(400, 200, 'bg').setScale(2);

    this.spriteBase = this.add.image(240, 420, 'sprite').setFlipX(true);
    this.spriteKey = this.add.image(1000, 400, 'sprite').enableFilters();
}

Создание параллельных фильтров

Фильтры в Phaser можно комбинировать. В данном примере мы создаем группу параллельных фильтров для спрайта spriteKey. Параллельные фильтры применяются к изображению одновременно, а не последовательно. Мы получаем доступ к внутренней системе фильтров объекта через this.spriteKey.filters.internal.

const parallelFilters = this.spriteKey.filters.internal.addParallelFilters();

Метод addParallelFilters() возвращает контейнер, внутри которого мы можем добавлять конкретные фильтры. В данном случае мы будем работать с верхним слоем (parallelFilters.top).

Фильтр Key: выделение целевого цвета

Первый фильтр, который мы добавляем — Key. Его задача — найти на изображении все пиксели, близкие к заданному цвету, и изолировать их.

const keyRed = parallelFilters.top.addKey({
    color: 0x241cb4,
    isolate: true,
    threshold: 0.4
});

Разберем параметры: - color: 0x241cb4 — целевой цвет в HEX-формате, который мы хотим выделить (в данном случае темно-синий). - isolate: true — ключевой параметр. Он указывает фильтру оставить только пиксели целевого цвета, а все остальные сделать прозрачными или невидимыми. - threshold: 0.4 — порог чувствительности (от 0 до 1). Чем выше значение, тем более широкий диапазон оттенков, близких к целевому цвету, будет захвачен фильтром. Значение 0.4 дает умеренную чувствительность.

Фильтр Color Matrix: трансформация цвета

После того как мы изолировали пиксели нужного цвета, можно их преобразовать. Для этого добавляется фильтр ColorMatrix, который предоставляет матрицу для комплексных цветовых преобразований.

const colorMatrix = parallelFilters.top.addColorMatrix();
colorMatrix.colorMatrix.hue(150).brightness(2, true);

Метод hue(150) поворачивает цветовой круг на 150 градусов. Это кардинально меняет оттенок выделенных синих пикселей (например, на оранжевый или желтый).

Метод brightness(2, true) увеличивает яркость. Первый аргумент — множитель (2 означает удвоение яркости). Второй аргумент true указывает, что яркость применяется ко всем каналам (R, G, B) равномерно, сохраняя цветовой баланс.

Поскольку фильтры добавлены параллельно в один слой (parallelFilters.top), они применяются к исходному изображению одновременно, но логически: сначала Key выделяет область, а ColorMatrix меняет ее свойства.

Настройка конфигурации игры

Весь эффект работает благодаря использованию WebGL-рендерера. Важно указать type: Phaser.WEBGL в конфигурации игры, так как фильтры не поддерживаются на Canvas-рендерере.

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

Параметр smoothPixelArt: true помогает сгладить пиксельную графику при масштабировании, что улучшает визуальное качество. backgroundColor задает темный фон для контраста с яркими спрайтами.

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

Комбинация фильтров Key и ColorMatrix открывает быстрый путь для программного ретекстуринга объектов в игре. Вы можете динамически менять цвета униформы команд, подсвечивать интерактивные элементы или создавать эффекты проклятья или благословения, трансформируя внешний вид персонажей. Для экспериментов попробуйте изменить параметр threshold у Key фильтра, чтобы захватить больше или меньше оттенков, или используйте другие методы colorMatrix, например .saturate() или .contrast(), для создания разнообразных визуальных стилей.