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

Наложение фильтров на изображения — мощный инструмент для мгновенного изменения визуального стиля вашей игры. Фильтр Quantize позволяет ограничить цветовую палитру спрайта, создавая эффект ретро-графики, пиксель-арта или постерного стиля. В этой статье разберем, как применить этот фильтр в Phaser и управлять его параметрами в реальном времени для создания динамичных визуальных эффектов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    image;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('anime-market', 'assets/pics/anime-market.png');
    }

    create ()
    {
        this.image = this.add.image(400, 300, 'anime-market').setScale(2);
        const quantize = this.image.enableFilters().filters.external.addQuantize({
            steps: [ 8, 4, 4, 1 ],
            mode: 1, // HSVA
            dither: true
        });

        const toggleQuantize = this.add.text(32, 32, 'Toggle quantization', { fontSize: 24 });
        const toggleColorSpace = this.add.text(32, 64, 'Toggle color space (HSV/RGB)', { fontSize: 24 });
        const toggleDither = this.add.text(32, 96, 'Toggle dither', { fontSize: 24 });
        const toggleSteps = this.add.text(32, 128, 'Toggle steps (more/less)', { fontSize: 24 });
        const status = this.add.text(32, 670, 'Status', { fontSize: 24 });

        let steps = 'low';
        const resetSteps = () => {
            switch (quantize.mode)
            {
                case 0:
                {
                    // RGB
                    quantize.steps = steps === 'low' ? [ 4, 4, 4, 1 ] : [ 8, 8, 8, 1 ]
                    break;
                }
                case 1:
                {
                    // HSV
                    quantize.steps = steps === 'low' ? [ 8, 4, 4, 1 ] : [ 16, 8, 8, 1 ]
                    break;
                }
            }
        };

        const updateStatus = () => {
            if (!quantize.active)
            {
                status.text = 'Quantize OFF';
                return;
            }
            status.text = `Color space ${quantize.mode === 0 ? 'RGB' : 'HSV'}, dither ${quantize.dither ? 'ON' : 'OFF'}, steps ${quantize.steps}`;
        };
        updateStatus();

        toggleQuantize.setInteractive().on('pointerdown', () => {
            quantize.active = !quantize.active;
            updateStatus();
        });
        toggleColorSpace.setInteractive().on('pointerdown', () => {
            quantize.mode = quantize.mode === 1 ? 0 : 1;
            resetSteps();
            updateStatus();
        });
        toggleDither.setInteractive().on('pointerdown', () => {
            quantize.dither = !quantize.dither;
            updateStatus();
        });
        toggleSteps.setInteractive().on('pointerdown', () => {
            steps = steps === 'low' ? 'high' : 'low';
            resetSteps();
            updateStatus();
        });
    }

    update (time, delta)
    {
        this.image.x = 16 * Math.sin(time / 765) + 640;
        this.image.y = 16 * Math.sin(time / 1000) + 512;
        this.image.rotation = 0.005 * Math.sin(time / 881);
    }
}

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

let game = new Phaser.Game(config);

Инициализация и настройка фильтра

В Phaser фильтры применяются к объектам, поддерживающим рендеринг, таким как Image. Первым шагом необходимо активировать систему фильтров для объекта.

this.image = this.add.image(400, 300, 'anime-market').setScale(2);
const quantize = this.image.enableFilters().filters.external.addQuantize({
    steps: [ 8, 4, 4, 1 ],
    mode: 1, // HSVA
    dither: true
});

Метод enableFilters() подготавливает объект к работе с фильтрами. Затем через filters.external.addQuantize() создается и добавляется непосредственно фильтр квантования. Конфигурация передается объектом с ключевыми параметрами: - steps: массив, определяющий количество шагов (уровней) для каждого канала цвета. Порядок каналов зависит от выбранного mode. - mode: цветовое пространство для квантования. `0— RGB,1` — HSVA. - dither: булево значение, включает или выключает дизеринг для сглаживания цветовых переходов.

Параметры фильтра и их влияние

Фильтр Quantize предоставляет свойства, которые можно менять динамически, сразу влияя на рендеринг.

// Активность фильтра
quantize.active = !quantize.active;

// Переключение цветового пространства
quantize.mode = quantize.mode === 1 ? 0 : 1;

// Включение/выключение дизеринга
quantize.dither = !quantize.dither;

// Изменение шагов квантования
quantize.steps = steps === 'low' ? [ 4, 4, 4, 1 ] : [ 8, 8, 8, 1 ];

- Свойство active полностью включает или выключает эффект фильтра. - mode переключает алгоритм квантования между RGB и HSVA. В HSV-режиме обычно логичнее сильнее квантовать оттенок (Hue), оставляя больше градаций насыщенности и значения, что лучше сохраняет восприятие изображения. - dither добавляет шум для имитации промежуточных цветов, что особенно полезно при малом количестве цветов. - steps — самый важный параметр. Чем меньше числа в массиве, тем более ограниченной и стилизованной будет палитра. Например, [2, 2, 2, 1] создаст изображение всего из 8 цветов.

Интерактивное управление и UI

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

const toggleQuantize = this.add.text(32, 32, 'Toggle quantization', { fontSize: 24 });
toggleQuantize.setInteractive().on('pointerdown', () => {
    quantize.active = !quantize.active;
    updateStatus();
});

Каждый текстовый объект делается интерактивным с помощью setInteractive(). При клике (pointerdown) меняется соответствующее свойство объекта фильтра quantize.

Функция resetSteps() обеспечивает согласованную смену параметра steps при переключении цветового пространства, предлагая разные предустановки для RGB и HSV-режимов.

const resetSteps = () => {
    switch (quantize.mode)
    {
        case 0: // RGB
            quantize.steps = steps === 'low' ? [ 4, 4, 4, 1 ] : [ 8, 8, 8, 1 ];
            break;
        case 1: // HSV
            quantize.steps = steps === 'low' ? [ 8, 4, 4, 1 ] : [ 16, 8, 8, 1 ];
            break;
    }
};

Динамика и анимация отфильтрованного объекта

Фильтры в Phaser применяются на этапе рендеринга. Это означает, что даже анимированный объект будет обработан фильтром каждый кадр.

В методе update исходного кода изображение движется и вращается по простой гармонической функции.

update (time, delta)
{
    this.image.x = 16 * Math.sin(time / 765) + 640;
    this.image.y = 16 * Math.sin(time / 1000) + 512;
    this.image.rotation = 0.005 * Math.sin(time / 881);
}

Фильтр Quantize не влияет на трансформации объекта (позицию, вращение, масштаб). Он модифицирует только цвета пикселей после применения всех трансформаций. Это позволяет создавать сложные эффекты, где стилизованный объект плавно движется по сцене, сохраняя свой "квантованный" вид.

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

Фильтр Quantize — это быстрый и эффективный способ добавить стилизацию под пиксель-арт или ретро-игры прямо в рантайме. Поэкспериментируйте: примените фильтр к анимированному спрайту персонажа, чтобы создать эффект "фантома" или "воспоминания". Попробуйте менять параметры steps в зависимости от игровых событий — например, уменьшать палитру при получении урона или в определенных локациях. Комбинируйте этот фильтр с другими, например, с Blur или Glow, для создания уникальных визуальных состояний.