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

Статичный текст — это скучно. В игровых интерфейсах, меню и диалогах хочется динамики. Класс `DynamicBitmapText` в Phaser позволяет управлять отображением каждого символа по отдельности через callback-функцию. Это открывает двери для создания уникальных эффектов: от легкой дрожи и волны до сложной анимации букв. В этой статье мы разберем, как с помощью `setDisplayCallback` добавить эффект хаотичного "вибления" (wibble) к bitmap-тексту, сделав его живым и привлекательным, а также совместим эту технику с базовой твин-анимацией всего объекта.

Версия 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.bitmapFont('desyrel', 'assets/fonts/bitmap/desyrel.png', 'assets/fonts/bitmap/desyrel.xml');
    }

    create()
    {
        var text = this.add.dynamicBitmapText(60, 200, 'desyrel', 'HELLO WORLD!', 64);

        text.setDisplayCallback(this.textCallback);

        this.tweens.add({
            targets: text,
            duration: 2000,
            delay: 2000,
            scaleX: 2,
            scaleY: 2,
            ease: 'Sine.easeInOut',
            repeat: -1,
            yoyo: true
        });
    }

    //  data = { index: index, charCode: charCode, x: x, y: y, scaleX: scaleX, scaleY: scaleY }
    textCallback (data)
    {
        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);

Загрузка bitmap-шрифта и создание динамического текста

Перед работой с текстом необходимо загрузить bitmap-шрифт. В отличие от системных шрифтов, bitmap-шрифт — это набор готовых изображений символов, что гарантирует идентичное отображение на всех устройствах.

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 мы создаем объект динамического текста. Ключевое отличие DynamicBitmapText от обычного BitmapText — возможность задать функцию обратного вызова для модификации свойств каждого символа.

create()
{
    var text = this.add.dynamicBitmapText(60, 200, 'desyrel', 'HELLO WORLD!', 64);
}

Аргументы при создании: позиция X, Y, ключ шрифта, строка текста и размер.

Сердце эффекта: функция обратного вызова `setDisplayCallback`

Метод setDisplayCallback — это главный инструмент. Он принимает функцию, которая будет вызвана для подготовки к отрисовке **каждого символа** в каждом кадре.

text.setDisplayCallback(this.textCallback);

Функция textCallback получает объект data с параметрами текущего символа. Важно: эта функция должна **вернуть** объект data, даже если вы его изменили.

Структура объекта data: - index: порядковый номер символа в строке. - charCode: код символа. - `x,y`: текущие координаты символа. - scaleX, scaleY: текущий масштаб символа.

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

//  data = { index: index, charCode: charCode, x: x, y: y, scaleX: scaleX, scaleY: scaleY }
textCallback (data)
{
    data.x = Phaser.Math.Between(data.x - 2, data.x + 2);
    data.y = Phaser.Math.Between(data.y - 4, data.y + 4);
    return data;
}

Phaser.Math.Between возвращает случайное целое число в заданном диапазоне. Смещение по Y (-4, +4) сделано больше, чем по X (-2, +2), чтобы дрожание казалось более естественным.

Сочетание с общей анимацией: работа твинов

Эффект callback-функции прекрасно комбинируется с системой анимаций Phaser (tweens). В примере мы добавляем твин, который циклически масштабирует весь текстовый объект.

this.tweens.add({
    targets: text, // Цель анимации — наш объект text
    duration: 2000, // Длительность одного цикла — 2 секунды
    delay: 2000, // Задержка перед стартом — 2 секунды
    scaleX: 2, // Конечный масштаб по X
    scaleY: 2, // Конечный масштаб по Y
    ease: 'Sine.easeInOut', // Плавная функция easing
    repeat: -1, // Бесконечное повторение
    yoyo: true // Движение "туда-обратно"
});

Важный момент: функция textCallback работает с **исходными** координатами символа внутри объекта. Даже когда scaleX и scaleY всего объекта меняются твином, callback-эффект дрожания применяется корректно, так как он модифицирует позицию символа до применения общего трансформа объекта. Это демонстрирует мощь и гибкость разделения логики: callback управляет символом, а твин — всем объектом в сцене.

Конфигурация игры и запуск сцены

Код завершается стандартной для Phaser 3 конфигурацией игры и ее инстанцированием.

const config = {
    type: Phaser.WEBGL, // Используем WebGL рендерер
    parent: 'phaser-example', // ID DOM-элемента для канваса
    scene: Example // Наша основная сцена
};

const game = new Phaser.Game(config);

Указание type: Phaser.WEBGL обеспечивает аппаратное ускорение графики, что важно для плавности эффектов, особенно при большом количестве анимированных объектов.

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

DynamicBitmapText с setDisplayCallback — это мощный инструмент для оживления текста в вашей игре. Мы реализовали простой, но эффектный "wibble"-эффект, который легко комбинируется с другими анимациями. **Идеи для экспериментов:** 1. Свяжите силу дрожания (Phaser.Math.Between) со значением из игрового процесса (например, здоровьем персонажа или уровнем напряжения). 2. Внутри callback используйте data.index для создания волнообразных эффектов, применяя синус или косинус к смещению символов. 3. Попробуйте анимировать не только `xиy, но иscaleX,scaleY` каждого символа для эффекта пульсации.