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

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

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

Живой запуск

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

Исходный код


const rainbowColor = [0xFF5757, 0xE8A241, 0x97FF7F, 0x52BFFF, 0x995DE8];
let rainbowColorIdx = 0;
let rainbowColorOffset = 0;
let delay = 0;
let rainbowWave = 0;

let jiggleText;
let rainbowText;

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

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.bitmapFont('desyrel', 'assets/fonts/bitmap/desyrel.png', 'assets/fonts/bitmap/desyrel.xml');
    }

    create ()
    {
        jiggleText = this.add.dynamicBitmapText(32, 100, 'desyrel', 'It\'s cold outside,\nthere\'s no kind of atmosphere', 64);

        rainbowText = this.add.dynamicBitmapText(32, 400, 'desyrel', 'HELLO WORLD', 96);

        jiggleText.setDisplayCallback(this.textCallback);

        rainbowText.setDisplayCallback(this.rainbowCallback);
    }

    update()
    {
        rainbowColorIdx = 0;

        if (delay++ === 6)
        {
            rainbowColorOffset = (rainbowColorOffset + 1) % (rainbowColor.length);
            delay = 0;
        }
    }

    rainbowCallback(data)
    {
        data.color = rainbowColor[(rainbowColorOffset + rainbowColorIdx) % rainbowColor.length];
        rainbowColorIdx = (rainbowColorIdx + 1) % (rainbowColor.length);
        data.y = Math.cos(rainbowWave + rainbowColorIdx) * 10;
        rainbowWave += 0.01;

        return data;
    }

    //  data = { color: color, index: index, charCode: charCode, x: x, y: y, scaleX: scaleX, scaleY: scaleY }
    textCallback (data)
    {
        if (data.index >= 5 && data.index <= 8)
        {
            data.x = Phaser.Math.Between(data.x - 2, data.x + 2);
            data.y = Phaser.Math.Between(data.y - 4, data.y + 4);
        }

        return data;
    }

}

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

const game = new Phaser.Game(config);

Что такое Display Callback?

Класс DynamicBitmapText в Phaser позволяет отображать текст, используя растровые шрифты (bitmap fonts). Его ключевая особенность — метод setDisplayCallback(callback). Этот метод назначает функцию обратного вызова (callback), которая выполняется для **каждого отображаемого символа** текста на каждом кадре.

Функция получает объект data с параметрами текущего символа и должна вернуть его обратно, возможно, с изменениями. Это и есть основа для персональной анимации каждого символа.

rainbowText.setDisplayCallback(this.rainbowCallback);

Структура данных для callback

Функция обратного вызова получает один аргумент — объект data. В примере этот объект подробно описан в комментарии к textCallback. Давайте разберем его поля.

* color: Цвет символа в числовом формате (как 0xFF5757). * index: Индекс (порядковый номер) символа в строке. * charCode: Код символа. * `x,y`: Текущие координаты символа. * scaleX, scaleY: Масштаб по осям.

Изменяя эти свойства внутри callback-функции, мы управляем отображением.

//  data = { color: color, index: index, charCode: charCode, x: x, y: y, scaleX: scaleX, scaleY: scaleY }
textCallback (data) {
    // Логика изменения данных символа
    return data;
}

Важно всегда возвращать измененный объект data из функции.

Эффект "Дрожания" (Jiggle)

Первый пример в коде (textCallback) создает эффект легкого дрожания для конкретного диапазона символов. Логика проста: мы определяем, попадает ли индекс текущего символа в нужный интервал, и если да — случайным образом смещаем его координаты.

В данном случае эффект применяется к символам с индексами от 5 до 8 (то есть к слову "cold"). Функция Phaser.Math.Between(min, max) каждый кадр генерирует случайное смещение относительно исходной позиции.

textCallback (data) {
    // Применяем эффект только к символам с 5-го по 8-й индекс
    if (data.index >= 5 && data.index <= 8) {
        data.x = Phaser.Math.Between(data.x - 2, data.x + 2);
        data.y = Phaser.Math.Between(data.y - 4, data.y + 4);
    }
    return data;
}

Эффект "Радужной волны" (Rainbow Wave)

Второй, более сложный пример (rainbowCallback), совмещает два эффекта: плавное изменение цвета по заданной палитре и волнообразное движение по вертикали.

**Цвет:** Глобальная переменная rainbowColorOffset медленно меняется в методе update(), создавая эффект "прокрутки" палитры. Для каждого символа мы берем цвет из массива rainbowColor, используя сумму этого смещения и локального индекса rainbowColorIdx. rainbowColorIdx увеличивается для каждого следующего символа в строке, создавая градиент.

**Движение:** Вертикальная позиция data.y вычисляется с помощью косинуса, что дает плавную волну. Переменная rainbowWave постоянно увеличивается, заставляя волну "бежать" вдоль текста.

rainbowCallback(data) {
    // Меняем цвет, используя смещение и индекс
    data.color = rainbowColor[(rainbowColorOffset + rainbowColorIdx) % rainbowColor.length];
    rainbowColorIdx = (rainbowColorIdx + 1) % (rainbowColor.length);

    // Задаем вертикальное движение по волне
    data.y = Math.cos(rainbowWave + rainbowColorIdx) * 10;
    rainbowWave += 0.01;

    return data;
}

Важные детали реализации

1. **Глобальные переменные:** Обратите внимание, что для работы эффектов используются глобальные переменные (rainbowColorIdx, rainbowWave и др.). Это нужно, чтобы сохранять состояние между вызовами callback для разных символов и кадров. В более сложном проекте лучше вынести эту логику в свойства сцены или отдельный класс. 2. **Сброс индекса:** В методе update() перед каждым новым кадром отрисовки сбрасывается rainbowColorIdx = 0. Это важно, потому что rainbowCallback вызывается для каждого символа заново, и отсчет индекса цвета должен начинаться с первого символа строки. 3. **Задержка (delay):** Переменная delay используется, чтобы замедлить изменение глобального смещения цвета (rainbowColorOffset), делая анимацию более плавной и не слишком быстрой.

update() {
    rainbowColorIdx = 0; // Сбрасываем для нового кадра
    if (delay++ === 6) { // Обновляем смещение цвета раз в 6 кадров
        rainbowColorOffset = (rainbowColorOffset + 1) % (rainbowColor.length);
        delay = 0;
    }
}

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

Метод setDisplayCallback для DynamicBitmapText — это мощный и гибкий инструмент для создания живого, динамического текста в Phaser. Вы можете управлять не только цветом и позицией, но и масштабом, прозрачностью (добавив свойство alpha в объект data) и другими параметрами. **Идеи для экспериментов:** * Создайте эффект "появления" текста, анимируя scale или alpha символов в зависимости от индекса или времени. * Реагируйте на ввод игрока: пусть текст "отскакивает" или меняет цвет при наведении курсора. * Скомбинируйте несколько callback-функций для разных строк или частей интерфейса. * Используйте физическое тело (тело Arcade Physics) для расчета позиции символов, создавая текст, который "падает" или сталкивается.