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

Работа с цветом — ключевой элемент визуального стиля игры. Phaser 3 предлагает не только базовое окрашивание спрайтов, но и целый набор режимов наложения оттенков (tint modes), которые позволяют создавать сложные визуальные эффекты, от свечения до цветовой коррекции, прямо на лету, без подготовки отдельных текстур. Эта статья покажет, как использовать `setTintMode()` для полного контроля над цветом ваших игровых объектов.

Версия 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('lulu', 'assets/pics/shocktroopers-lulu2.png');
    }

    create ()
    {
        const setWhite = this.add.image(64, 64, 'lulu')
        .setScale(1/4)
        .setInteractive({ pixelPerfect: true })
        .setTintMode(Phaser.TintModes.FILL);

        const setBlack = this.add.image(64, 64 * 2, 'lulu')
        .setScale(1/4)
        .setInteractive({ pixelPerfect: true })
        .setTintMode(Phaser.TintModes.FILL)
        .setTint(0x000000);

        const setHot = this.add.image(64, 64 * 3, 'lulu')
        .setScale(1/4)
        .setInteractive({ pixelPerfect: true })
        .setTintMode(Phaser.TintModes.FILL)
        .setTint(0xffcc88, 0xffcc88, 0xcc4444, 0xcc4444);

        const setRGB = this.add.image(64, 64 * 4, 'lulu')
        .setScale(1/4)
        .setInteractive({ pixelPerfect: true })
        .setTintMode(Phaser.TintModes.FILL)
        .setTint(0xffffff, 0xff0000, 0x00ff00, 0x0000ff);

        const setMultiply = this.add.text(64, 64 * 5, 'MULTIPLY')
        .setInteractive();

        const setFill = this.add.text(64, 64 * 5.5, 'FILL')
        .setInteractive();

        const setAdd = this.add.text(64, 64 * 6, 'ADD')
        .setInteractive();

        const setScreen = this.add.text(64, 64 * 6.5, 'SCREEN')
        .setInteractive();

        const setOverlay = this.add.text(64, 64 * 7, 'OVERLAY')
        .setInteractive();

        const setHardLight = this.add.text(64, 64 * 7.5, 'HARD_LIGHT')
        .setInteractive();

        const texts = [ setMultiply, setFill, setAdd, setScreen, setOverlay, setHardLight ];

        const demo = this.add.image(500, 320, 'lulu')
        .setScale(3);

        setWhite.on('pointerdown', () => {
            demo.setTint(0xffffff);
        });
        setBlack.on('pointerdown', () => {
            demo.setTint(0x000000);
        });
        setHot.on('pointerdown', () => {
            demo.setTint(0xffcc88, 0xffcc88, 0xcc4444, 0xcc4444);
        });
        setRGB.on('pointerdown', () => {
            demo.setTint(0xffffff, 0xff0000, 0x00ff00, 0x0000ff);
        });
        setMultiply.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.MULTIPLY);
        });
        setFill.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.FILL);
        });
        setAdd.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.ADD);
        });
        setScreen.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.SCREEN);
        });
        setOverlay.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.OVERLAY);
        });
        setHardLight.on('pointerdown', function () {
            texts.forEach(text => text.clearTint());
            this.setTint(0xffff00);
            demo.setTintMode(Phaser.TintModes.HARD_LIGHT);
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#222288',
    pixelArt: true,
    scene: Example
};

const game = new Phaser.Game(config);

Что такое Tint Mode?

Метод setTint() в Phaser позволяет окрашивать изображение в заданный цвет. По умолчанию используется режим MULTIPLY, который умножает цвета текстуры на значение тинта.

Однако Phaser предоставляет шесть различных режимов наложения через перечисление Phaser.TintModes. Каждый режим применяет уникальную математическую операцию для смешивания цвета тинта с исходными пикселями изображения, что даёт совершенно разные визуальные результаты.

В примере создаётся демонстрационная сцена с одним крупным спрайтом и набором кнопок. Нажатие на кнопки меняет как цвет тинта, так и его режим наложения для основного спрайта.

Базовое окрашивание и режим FILL

Прежде чем перейти к режимам, давайте посмотрим, как задать простой однотонный цвет и что делает режим FILL.

В примере создаются четыре небольших спрайта-кнопки. Первые три используют режим FILL.

const setWhite = this.add.image(64, 64, 'lulu')
.setScale(1/4)
.setInteractive({ pixelPerfect: true })
.setTintMode(Phaser.TintModes.FILL);

Этот код создаёт изображение, устанавливает для него режим тинта FILL и делает его интерактивным. Обратите внимание, что сам тинт здесь не задан, поэтому спрайт отображается в оригинальных цветах, но готов к окрашиванию.

Режим FILL полностью заменяет цвет пикселя на цвет тинта, игнорируя исходную яркость текстуры. Это полезно для создания силуэтов или полной перекраски спрайта в плоский цвет.

const setBlack = this.add.image(64, 64 * 2, 'lulu')
.setScale(1/4)
.setInteractive({ pixelPerfect: true })
.setTintMode(Phaser.TintModes.FILL)
.setTint(0x000000);

Здесь спрайт сразу окрашивается в чёрный цвет (0x000000) с помощью setTint(). В режиме FILL это даст полностью чёрный силуэт.

Метод setTint() также поддерживает задание отдельного цвета для каждого угла спрайта (top-left, top-right, bottom-left, bottom-right), что позволяет создавать градиенты.

const setRGB = this.add.image(64, 64 * 4, 'lulu')
.setScale(1/4)
.setInteractive({ pixelPerfect: true })
.setTintMode(Phaser.TintModes.FILL)
.setTint(0xffffff, 0xff0000, 0x00ff00, 0x0000ff);

Этот код окрашивает углы спрайта в белый, красный, зелёный и синий цвета соответственно, создавая цветовой градиент в режиме FILL.

Интерактивное переключение режимов

Основная демонстрация построена на обработчиках событий pointerdown. При нажатии на текстовую кнопку с названием режима происходит три действия: 1. Сбрасывается подсветка (clearTint()) со всех текстовых кнопок. 2. Активная кнопка подсвечивается жёлтым цветом (this.setTint(0xffff00)). 3. Для крупного демо-спрайта устанавливается выбранный режим наложения.

setMultiply.on('pointerdown', function () {
    texts.forEach(text => text.clearTint());
    this.setTint(0xffff00);
    demo.setTintMode(Phaser.TintModes.MULTIPLY);
});

Аналогичные обработчики созданы для всех шести режимов: MULTIPLY, FILL, ADD, SCREEN, OVERLAY и HARD_LIGHT. Это позволяет в реальном времени сравнивать их визуальный эффект на одном и том же спрайте с разными предустановленными цветами (белый, чёрный, тёплый, RGB-градиент).

Обзор всех режимов TintModes

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

* Phaser.TintModes.MULTIPLY (Умножение): Значение по умолчанию. Умножает каналы (R, G, B) текстуры на каналы тинта. Затемняет изображение. Белый тинт (0xffffff) не вносит изменений. * Phaser.TintModes.FILL (Заполнение): Полностью заменяет цвет пикселя на цвет тинта, не учитывая исходную текстуру (кроме альфа-канала). * Phaser.TintModes.ADD (Сложение): Складывает значения каналов текстуры и тинта. Часто создаёт эффект свечения или осветления. * Phaser.TintModes.SCREEN (Экран): Инвертирует оба цвета, перемножает их, и снова инвертирует результат. Даёт эффект осветления, противоположный MULTIPLY. Полезен для эффектов света. * Phaser.TintModes.OVERLAY (Наложение): Комбинация MULTIPLY и SCREEN. Затемняет тёмные области и осветляет светлые, увеличивая контраст. * Phaser.TintModes.HARD_LIGHT (Жёсткий свет): Похож на OVERLAY, но основан на цвете тинта. Если тинт светлый (>0.5), применяется осветление (SCREEN), если тёмный — затемнение (MULTIPLY).

Для активации любого режима используется один метод:

yourSprite.setTintMode(Phaser.TintModes.ADD);

Практические советы по использованию

1. **Производительность:** Режимы тинта выполняются на GPU (при использовании WebGL) и очень эффективны. Это дешевле, чем загрузка множества предварительно окрашенных текстур. 2. **Совместное использование:** Режим тинта и конкретный цвет задаются независимо. Вы можете установить режим один раз при создании объекта, а затем динамически менять только цвет с помощью setTint(). 3. **Для текста и графики:** Режимы работают не только с изображениями (Image), но и с текстом (Text) и графикой (Graphics), как видно в примере с кнопками. 4. **Pixel Art:** Обратите внимание на конфигурацию игры в примере. Установлена опция pixelArt: true, которая отключает сглаживание для пиксельной графики. Это важно, чтобы тинт не "размывал" чёткие края пикселей.

const config = {
    type: Phaser.WEBGL,
    pixelArt: true,
    // ... другие параметры
};

5. **Интерактивность:** В примере для кнопок-спрайтов используется setInteractive({ pixelPerfect: true }). Это означает, что клик сработает только по непрозрачным пикселям изображения, что улучшает юзабилити для объектов сложной формы.

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

Режимы наложения оттенков в Phaser 3 — это мощный и производительный инструмент для динамического управления цветом. Вместо подготовки артов под каждый возможный цветовой вариант вы можете менять внешний вид объектов кодом, создавая эффекты повреждения, свечения, ночного видения или сезонных изменений. **Идеи для экспериментов:** 1. Анимируйте изменение цвета тинта с помощью твинов, чтобы объект плавно переливался разными цветами в режиме ADD (для свечения) или OVERLAY (для пульсирующего контраста). 2. Используйте HARD_LIGHT с цветом вспышки для симуляции эффекта попадания пули или взрыва рядом с объектом. 3. Комбинируйте режимы на разных слоях. Например, наложите на спрайт полупрозрачную графику с тинтом в режиме SCREEN для создания области локального освещения. 4. Примените градиентный тинт (с разными цветами для углов) в режиме MULTIPLY, чтобы создать реалистичное цветовое освещение от нескольких источников.