О чем этот пример
При работе с текстом в 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 или нестандартных шрифтов.
