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

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

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

Живой запуск

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

Исходный код


let i = 0;
let hsv = [];

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('knighthawks', 'assets/fonts/retro/knight3.png');
    }

    create ()
    {
        hsv = Phaser.Display.Color.HSVColorWheel();

        const config = {
            image: 'knighthawks',
            width: 31,
            height: 25,
            chars: Phaser.GameObjects.RetroFont.TEXT_SET6,
            charsPerRow: 10,
            spacing: { x: 1, y: 1 }
        };

        this.cache.bitmapFont.add('knighthawks', Phaser.GameObjects.RetroFont.Parse(this, config));

        const text = this.add.dynamicBitmapText(0, 300, 'knighthawks', 'PHASER 3').setScale(4);

        text.setDisplayCallback(this.textCallback);
    }

    textCallback (data)
    {
        data.tint.topLeft = hsv[Math.floor(i)].color;
        data.tint.topRight = hsv[359 - Math.floor(i)].color;
        data.tint.bottomLeft = hsv[359 - Math.floor(i)].color;
        data.tint.bottomRight = hsv[Math.floor(i)].color;

        i += 0.1;

        if (i >= hsv.length)
        {
            i = 0;
        }

        return data;
    }

}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    pixelArt: true,
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка: загрузка и создание растрового шрифта

Прежде чем выводить текст, необходимо подготовить ресурсы. В методе preload() загружается изображение, содержащее набор символов растрового шрифта.

this.load.image('knighthawks', 'assets/fonts/retro/knight3.png');

Ключевой этап происходит в create(). Сначала генерируется цветовая палитра HSV (цветовой круг), которая будет использоваться для окрашивания. Затем создается конфигурационный объект для парсинга растрового шрифта. В нем указывается ключ загруженного изображения, размеры одного символа, набор используемых символов (в данном случае TEXT_SET6), количество символов в строке изображения и отступы.

const config = {
    image: 'knighthawks',
    width: 31,
    height: 25,
    chars: Phaser.GameObjects.RetroFont.TEXT_SET6,
    charsPerRow: 10,
    spacing: { x: 1, y: 1 }
};

С помощью Phaser.GameObjects.RetroFont.Parse этот конфиг преобразуется в данные, которые Phaser понимает как шрифт, и добавляется в кеш под ключом 'knighthawks'.

this.cache.bitmapFont.add('knighthawks', Phaser.GameObjects.RetroFont.Parse(this, config));

Создание динамического текста

Теперь можно создать сам текстовый объект. Используется add.dynamicBitmapText. Важное отличие от обычного bitmapText — возможность применять callback-функцию к каждому символу для изменения его свойств перед отрисовкой.

const text = this.add.dynamicBitmapText(0, 300, 'knighthawks', 'PHASER 3').setScale(4);

Связь между текстовым объектом и функцией обратного вызова устанавливается методом setDisplayCallback. Именно эта функция, textCallback, будет управлять внешним видом каждого символа.

text.setDisplayCallback(this.textCallback);

Магия в деталях: функция обратного вызова

Функция textCallback — сердце анимации. Она вызывается для каждого символа в кадре. Ей передается объект data, содержащий свойства символа, такие как его индекс, код, положение и, что важно для нас, — объект tint.

Объект tint позволяет задать цвет для каждого из четырех углов символа, создавая градиентную заливку. В примере цвета берутся из заранее созданного массива hsv (цветового круга).

data.tint.topLeft = hsv[Math.floor(i)].color;
data.tint.topRight = hsv[359 - Math.floor(i)].color;
data.tint.bottomLeft = hsv[359 - Math.floor(i)].color;
data.tint.bottomRight = hsv[Math.floor(i)].color;

Глобальная переменная `i` увеличивается с каждым вызовом, что приводит к плавному переходу цветов по кругу. Условие сбрасывает счетчик, когда проход по всей палитре завершен.

i += 0.1;
if (i >= hsv.length) {
    i = 0;
}

Функция обязательно должна возвращать измененный объект data.

return data;

Именно этот механизм позволяет создавать сложные пошаговые анимации, где цвет каждого символа зависит от его позиции или глобального состояния игры.

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

Использование DynamicBitmapText с callback-функцией открывает широкие возможности для кастомизации текста в Phaser. Вы можете не только менять цвет, но и управлять прозрачностью, смещением, масштабом каждого символа на лету. Для экспериментов попробуйте привязать изменение цвета не к глобальному счетчику, а к позиции символа (data.index) или скорости движения игрового объекта. Это можно использовать для создания эффекта "волны", пробегающей по тексту, или для визуальной обратной связи, когда текст реагирует на действия игрока.