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

При работе с текстом в Phaser разработчики часто используют `setLetterSpacing` для настройки межбуквенного интервала. Однако существует тонкий момент: стандартный механизм переноса слов (`wordWrap`) не учитывает это свойство при расчёте границ строк, что приводит к визуальным артефактам — текст может выходить за пределы заданной области. Эта статья разбирает проблему на примере из официального репозитория и объясняет, какую роль играет параметр `useAdvancedWrap` в решении этой задачи.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
    }

    create ()
    {
        const longString = 'This is very long text that should wrap to another line but when letter spacing is applied, the wrapping doesn\'t get adjusted.';
        const style = {
            align: 'center',
            color: '#ffffff',
            wordWrap: {
                width: 250,
                // useAdvancedWrap: true
            }
        };

        // add a rect behind the text to show where the bounds should be
        const rect = this.add.rectangle(this.scale.width / 4, this.scale.height / 4, 250, 250, 0xffffff, 0).setStrokeStyle(2, 0xff0000);
        this.text = this.add.text(rect.x, rect.y, longString, style).setOrigin(0.5);
        rect.setSize(250, this.text.height);

        // add a rect behind the text to show where the bounds should be
        const rectForLetterSpacing = this.add.rectangle(this.scale.width / 4 * 3, this.scale.height / 4, 250, 250, 0xffffff, 0).setStrokeStyle(2, 0xff0000);
        this.textWithLetterSpacing = this.add.text(rectForLetterSpacing.x, rectForLetterSpacing.y, longString, style).setOrigin(0.5)
            .setLetterSpacing(10); // note: letter spacing is missing from the style config
        rectForLetterSpacing.setSize(250, this.textWithLetterSpacing.height);

        // add a rect behind the text to show where the bounds should be
        const rectForLetterSpacingNeg = this.add.rectangle(this.scale.width / 4, this.scale.height / 4 * 3, 250, 250, 0xffffff, 0).setStrokeStyle(2, 0xff0000);
        this.textWithLetterSpacingNeg = this.add.text(rectForLetterSpacingNeg.x, rectForLetterSpacingNeg.y, longString, style).setOrigin(0.5)
            .setLetterSpacing(-2); // note: letter spacing is missing from the style config
        rectForLetterSpacingNeg.setSize(250, this.textWithLetterSpacingNeg.height);
    }
}

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

const game = new Phaser.Game(config);

Суть проблемы: `wordWrap` игнорирует межбуквенный интервал

В примере создаются три текстовых блока с одинаковым длинным содержимым и ограничением по ширине в 250 пикселей. Первый блок выводится без дополнительного интервала, второй — с увеличенным интервалом (setLetterSpacing(10)), третий — с уменьшенным (setLetterSpacing(-2)). Для наглядности каждый текст помещён в красную рамку, размеры которой динамически подстраиваются под высоту текстового объекта.

Проблема становится очевидной при запуске: тексты с применённым setLetterSpacing выходят за пределы своей рамки по ширине, хотя высота рамки корректно обновляется. Это происходит потому, что внутренний расчёт переноса строк в Phaser по умолчанию оперирует только стандартной шириной символов, не учитывая модификаторы межбуквенного интервала.

Анализ исходного кода примера

Давайте посмотрим, как настроен объект стиля для текста в примере. Ключевой параметр — wordWrap.width.

const style = {
    align: 'center',
    color: '#ffffff',
    wordWrap: {
        width: 250,
        // useAdvancedWrap: true
    }
};

Текст создаётся с помощью метода this.add.text(), после чего к нему применяется setLetterSpacing(). Обратите внимание: межбуквенный интервал задаётся отдельным методом, его нельзя указать непосредственно в объекте стиля на момент создания текста.

this.textWithLetterSpacing = this.add.text(rectForLetterSpacing.x, rectForLetterSpacing.y, longString, style).setOrigin(0.5)
    .setLetterSpacing(10);

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

Решение: включение `useAdvancedWrap`

В конфигурации wordWrap закомментирована строка useAdvancedWrap: true. Именно этот параметр активирует более точный алгоритм расчёта переноса, который учитывает дополнительные трансформации текста, включая межбуквенный интервал.

Чтобы исправить пример, необходимо раскомментировать эту опцию:

wordWrap: {
    width: 250,
    useAdvancedWrap: true
}

После этого механизм переноса начнёт учитывать реальную визуальную ширину строки с учётом letterSpacing. Текст будет корректно вписываться в заданную ширину рамки. Важно понимать, что useAdvancedWrap использует более ресурсоёмкие вычисления (измеряет ширину каждого слова через Canvas API), поэтому включайте его только когда это действительно необходимо — например, при использовании нестандартного интервала или масштабирования.

Практическое применение и важные детали

1. **Когда использовать**: useAdvancedWrap необходим, если вы динамически меняете свойства текста, влияющие на его ширину (setLetterSpacing, setScale), и используете перенос строк. 2. **Производительность**: Поскольку алгоритм измеряет текст в контексте рендеринга, избегайте его постоянного включения для статического текста или в критичных к производительности сценах (например, для каждого кадра). 3. **Работа с рамками**: В примере размер красной рамки обновляется через setSize(), используя высоту текстового объекта. Ширина рамки остаётся фиксированной (250px) — это и есть зона, в которую должен вписаться текст.

rect.setSize(250, this.text.height);

Этот приём полезен для визуальной отладки границ любого игрового объекта.

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

Межбуквенный интервал в Phaser ломает стандартный перенос текста, но проблема легко решается активацией useAdvancedWrap в настройках wordWrap. Для экспериментов попробуйте динамически менять letterSpacing с помощью ползунка и наблюдать, как текст перестраивается с включённой и выключенной опцией. Также можно исследовать влияние на перенос других свойств, например, setScale или нестандартных шрифтов.